Requests are authenticated using a shared webhook secret configured on the Lekhapal platform connection. There is no Bearer token; the HMAC signature in the X-Webhook-Signature header serves as the sole credential.
401 Unauthorized. There is no unauthenticated fallback.| Header | Required | Description |
|---|---|---|
| Content-Type | Required | Must be application/json |
| X-Webhook-Signature | Required | HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret. See §02 for computation. |
| X-Lekhapal-Event | Optional | Explicit event name (e.g. sale.created). Falls back to the event field in the request body if omitted. |
The signature authenticates the request body and prevents replay or tampering. Compute it by running HMAC-SHA256 over the exact raw body bytes you will send, using the webhook secret as the key, then encode the result as a lowercase hexadecimal string.
Serialise the body
Produce the exact JSON string you will send as the request body. Do not pretty-print unless you send those exact bytes — any whitespace difference will break the signature.
Compute HMAC-SHA256
Key: your webhook_secret. Message: the raw body string. Output: lowercase hex digest (no prefix).
Set the header
Add X-Webhook-Signature: <hex-digest> to the request. No sha256= prefix is needed.
401 response.
All actions share the same endpoint URL. The event field in the JSON body determines which Lekhapal action is triggered. Supported events are listed below.
All transactional resources (sales, purchases, payments, expenses) follow a common lifecycle. Only approved records affect accounting ledgers. A voided record is soft-deleted and cannot be modified further.
| Event suffix | Action | Ledger impact |
|---|---|---|
| .created | Creates a new record in pending status |
None — draft only |
| .updated | Updates fields on an existing record. Pass the record's id. |
None while pending |
| .approved | Finalises the record — posts to accounting ledgers | ✓ Posts journal entries |
| .voided | Soft-deletes the record with an optional void_reason |
Reverses any posted entries |
Create and manage sales invoices with line-item detail. The details array must contain at least one product line.
sale.created
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Required | "sale.created" |
| date | string | Required | Invoice date in YYYY-MM-DD format |
| client_id | uuid | Required | ID of the customer (party) record |
| warehouse_id | string | Required | Warehouse identifier |
| currency_id | uuid | Required | Currency record ID |
| exchange_rate_to_base | number | Required | Exchange rate to base currency. Use 1 for base currency. |
| GrandTotal | number | Required | Total including tax and shipping, excluding discounts already applied |
| statut | string | Required | "pending" or "approved" |
| tax_rate | number | Required | Tax rate percentage (e.g. 13 for 13%) |
| TaxNet | number | Required | Total tax amount |
| discount | number | Required | Header-level discount value |
| discount_type | string | Required | "percentage" or "fixed" |
| shipping | number | Required | Shipping charge amount |
| details | array | Required | Line items array — see detail fields below |
| notes | string | Optional | Free-text notes |
| credit_terms_id | uuid|null | Optional | Payment credit terms |
| is_export_sales | boolean | Optional | Flag for export sales |
| tds_applicable | boolean | Optional | Whether TDS is applicable |
details[ ] — Line Item Fields
| Field | Type | Description |
|---|---|---|
| product_id | uuid | Lekhapal product ID |
| product_variant_id | uuid|null | Variant ID, or null if no variant |
| quantity | number | Quantity sold |
| Unit_price | number | Unit selling price (before tax) |
| Net_price | number | Price after discount, before tax |
| tax_percent | number | Tax percentage for this line |
| tax_method | string | "1" = inclusive, "2" = exclusive |
| discount | number | Line-level discount value |
| discount_Method | string | "fixed" or "percentage" |
| DiscountNet | number | Computed discount amount |
| subtotal | string | Line total as string (e.g. "275.00") |
| sale_unit_id | uuid | Unit of measure for sale |
| detail_id | number | Sequence number within this invoice (1, 2, 3…) |
sale.updated
Same structure as sale.created plus a required id field (the Lekhapal sale UUID to update) and the event set to "sale.updated". Pass the full details array.
sale.approved & sale.voided
Record a credit note against an existing approved sale. Must reference the original sale_id.
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Required | "sale_return.created" |
| date | string | Required | Return date YYYY-MM-DD |
| client_id | uuid | Required | Customer ID |
| sale_id | uuid | Required | Original sale UUID being returned |
| warehouse_id | string | Required | Warehouse |
| currency_id | uuid | Required | Currency |
| exchange_rate_to_base | number | Required | Exchange rate |
| GrandTotal | number | Required | Return total |
| statut | string | Required | "pending" |
| tax_rate | number | Required | Tax rate % |
| TaxNet | number | Required | Tax amount |
| discount | number | Required | Discount |
| discount_type | string | Required | "fixed" or "percentage" |
| shipping | number | Required | Shipping charge |
| details | array | Required | Returned line items (same structure as sale details) |
Create and manage purchase invoices from suppliers. Uses supplier_id and cost fields (Unit_cost, Net_cost) instead of client and price fields.
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Required | "purchase.created" |
| date | string | Required | YYYY-MM-DD |
| supplier_id | uuid | Required | Supplier (party) ID |
| warehouse_id | string | Required | Warehouse |
| currency_id | uuid | Required | Currency |
| exchange_rate_to_base | number | Required | Exchange rate |
| GrandTotal | number | Required | Invoice total |
| statut | string | Required | "pending" |
| reference | string | Optional | Supplier invoice / reference number |
| tax_rate | number | Required | Tax rate % |
| TaxNet | number | Required | Tax amount |
| discount | number | Required | Discount |
| discount_type | string | Required | "percentage" or "fixed" |
| shipping | number | Required | Freight / shipping |
| is_import_purchases | boolean | Optional | Flag for import purchases |
| details | array | Required | Line items — use Unit_cost, Net_cost |
Record returns of goods back to a supplier (debit note) against an existing purchase. Requires the original purchase_id.
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Required | "purchase_return.created" |
| date | string | Required | YYYY-MM-DD |
| supplier_id | uuid | Required | Supplier ID |
| purchase_id | uuid | Required | Original purchase UUID |
| warehouse_id | string | Required | Warehouse |
| currency_id | uuid | Required | Currency |
| exchange_rate_to_base | number | Required | Exchange rate |
| GrandTotal | number | Required | Return total |
| statut | string | Required | "pending" |
| tax_rate / TaxNet / discount / shipping | number | Required | Same as Purchase |
| details | array | Required | Returned line items |
Record a payment received from a customer. Payments are applied to the customer's outstanding balance.
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Required | "customer_payment.created" |
| date | string | Required | Payment date YYYY-MM-DD |
| client_id | uuid | Required | Customer (party) ID |
| warehouse_id | string | Required | Warehouse |
| account_id | uuid | Required | Bank / cash account to deposit into |
| amount | number | Required | Payment amount |
| payment_mode | string | Required | e.g. "Cash", "Bank Transfer", "Cheque" |
| payment_reference | string | Optional | Cheque number, transfer reference, etc. |
| currency_id | uuid | Required | Currency |
| exchange_rate | number | Required | Exchange rate |
| bank_charge_applicable | boolean | Optional | Whether a bank charge is deducted |
| bank_charge_amount | number|null | Optional | Bank charge amount |
| tds_applicable | boolean | Optional | Whether TDS is withheld |
| tds_amount | number | Optional | TDS amount |
| notes | string | Optional | Free-text note |
Record a payment made to a supplier. Identical structure to Customer Payment — uses supplier_id instead of client_id.
Record business expenses categorised against chart-of-account lines. The details array maps each expense line to a chart-of-account entry.
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Required | "expense.created" |
| date | string | Required | Expense date YYYY-MM-DD |
| due_date | string | Optional | Payment due date YYYY-MM-DD |
| supplier_id | uuid | Required | Supplier / vendor party ID |
| warehouse_id | string | Required | Warehouse |
| currency_id | uuid | Required | Currency |
| exchange_rate_to_base | number | Required | Exchange rate |
| reference | string | Optional | Bill / voucher reference |
| amount | number | Required | Total expense amount |
| TaxNet | number | Required | Tax amount |
| note | string | Optional | Free-text note |
| details | array | Required | Expense lines — see below |
details[ ] — Expense Line Fields
| Field | Type | Description |
|---|---|---|
| account | object | Chart-of-account object with at minimum id, name, type |
| amount | string|number | Amount for this expense line |
| tax_percent | string | Tax percentage or "N/A" |
| subtotal | number | Line subtotal |
Create or update a party (customer or supplier) record in Lekhapal. Update requires the party's id.
| Field | Type | Required | Description |
|---|---|---|---|
| event | string | Required | "party.created" or "party.updated" |
| id | uuid | Update only | Existing party UUID — required for party.updated |
| name | string | Required | Full name of the party |
| string | Optional | Email address | |
| phone | string | Optional | Contact number |
| country | string | Optional | Country name (e.g. "Nepal") |
| city | string|null | Optional | City |
| address | string | Optional | Street address |
| pan | string|null | Optional | PAN / tax identification number |
All responses are JSON. A 200 confirms the event was accepted and queued for processing — it does not guarantee the downstream action succeeded.
{"message":"Received."}{"error":"Unauthorized."}{"error":"Unknown channel."} or {"error":"No active connection for this channel."}The webhook endpoint enforces a sliding-window rate limit of 120 requests per 1-minute window per IP address.
429 response. The server does not send a Retry-After header — assume a 60-second cooldown.Common rejection reasons and how to resolve them.
| Reason | HTTP | Resolution |
|---|---|---|
| connection_has_no_secret | 401 | Ask the Lekhapal admin to configure a webhook secret on the custom_api connection. |
| missing_signature_header | 401 | The X-Webhook-Signature header was not sent. Ensure your signing logic sets this header on every request. |
| signature_mismatch | 401 | The computed HMAC does not match. Verify you are signing the exact raw body bytes — not a re-serialised or pretty-printed copy. |
| timestamp_too_old | 401 | If you send X-Webhook-Timestamp, the request must arrive within 5 minutes of that timestamp. Remove the header if you are not using the timestamp-based signing variant. |
| Unknown channel | 404 | The channel slug in the URL is wrong. Use custom_api exactly. |
| No active connection | 404 | No enabled platform connection exists for custom_api. Ask admin to activate the connection in Lekhapal settings. |
X-Webhook-Timestamp header (Unix seconds), the server switches to timestamp-prefixed signing: HMAC-SHA256("{timestamp}.{body}") and expects a sha256= prefix on the signature. The Postman collection does not use timestamps — simply sign the raw body without a prefix for the simplest integration.