Skip to content
Webhooks

Webhooks

BigLedger can POST to a URL of yours when something changes in a tenant, so your system does not have to poll for it.

This page documents what that mechanism does and — at least as important — what it does not do. Read the guarantees section before you design around it.

What it is

You register a subscription: a URL, a topic, and optionally one HTTP header for the receiver to authenticate with. When a matching change happens in that tenant, BigLedger POSTs a JSON body to your URL. Every attempt is written to a delivery log.

Topics are strings such as CUSTOMER_CREATED, INTERNAL_SALES_INVOICE_CREATED or INVENTORY_UPDATED. A subscription points at one topic. For several topics, register several subscriptions.

What it does not do

Four absences, all of which change how you should build.

There is no signature. BigLedger does not sign the payload — no HMAC, no shared signing secret, no timestamp or nonce. The only credential the receiver gets is one static header name and value that you chose when you created the subscription. Treat your endpoint as unauthenticated in practice: put it behind a hard-to-guess path, allowlist by source address if you can, and re-read the record through the API before you act on it rather than trusting the body.
There is no retry. One attempt, ever. If your endpoint returns a 500, or times out, or is mid-deploy, the event is gone — there is no backoff, no dead-letter queue and no replay endpoint. Nothing tells you it was lost. Webhooks are a hint that something changed, not a delivery guarantee. Pair every subscription with a periodic reconciliation pull on updated_date_from (see Getting Started). That pull is what makes your integration correct; the webhook only makes it fast.
There is no alerting. A subscription record has fields for success and failure notification by email and SMS. They are loaded onto the in-memory subscription and never looked at again: no mail or SMS sender exists anywhere in the delivery path. No message is ever sent, on success or on failure.
You cannot browse the delivery log. Every attempt is recorded — what was sent, what came back, the HTTP status, and the error on a transport failure — but the endpoint that lists those records reads the wrong database and returns an empty list on every tenant. Fetching one by its identifier works, and you have no way to obtain an identifier. In practice: log on your side, and treat the platform’s delivery log as unavailable until this is fixed.

Setting one up

Step 1 — make sure the topic exists

A subscription points at a topic record, not at a topic string, so the record has to exist first.

curl -s "https://api-etl.akaun.com/core2/tnt/dm/webhook-topic/query?limit=200" \
  -H "AccessId: $BLG_ACCESS_ID" -H "AccessKey: $BLG_ACCESS_KEY" -H "tenantCode: $BLG_TENANT_CODE"

If the list is empty, the tenant has never been seeded. Seeding is one call and it is idempotent:

curl -s -X POST "https://api-etl.akaun.com/core2/tnt/dm/webhook-topic/populate-default" \
  -H "AccessId: $BLG_ACCESS_ID" -H "AccessKey: $BLG_ACCESS_KEY" -H "tenantCode: $BLG_TENANT_CODE"

That inserts the platform’s built-in list of 54 topics. Take the guid of the one you want from the query above.

The built-in list is a seed, not the full set — and it does not match what actually fires. More than seventy topic codes that BigLedger genuinely emits are absent from the built-in list, so populate-default never creates them and nothing in the product lists them. Conversely, at least one seeded topic can never fire: the built-in list spells the branch-deleted topic BRANCH_DELTED while the platform emits BRANCH_DELETED, so a subscription to the offered topic receives nothing, forever.

If the event you need is not in the seeded list, do not assume it does not exist — open an issue at BigLedger-Support/public and ask for the exact topic code, then create the topic record yourself with POST /core2/tnt/dm/webhook-topic. The code must match the platform’s string exactly; there is no fuzzy matching and no error if it never matches.

Step 2 — create the subscription

curl -s -X POST "https://api-etl.akaun.com/core2/tnt/dm/webhook-subscription" \
  -H "AccessId: $BLG_ACCESS_ID" -H "AccessKey: $BLG_ACCESS_KEY" \
  -H "tenantCode: $BLG_TENANT_CODE" -H "Content-Type: application/json" \
  -d '{
        "bl_webhook_subscription_hdr": {
          "topic_hdr_guid": "<guid of the topic record>",
          "url": "https://hooks.gadgetsphere.example/blg/sales-invoice-created",
          "auth_header_name": "X-GadgetSphere-Token",
          "auth_header_value": "<a long random string>",
          "status": "ACTIVE"
        }
      }'
FieldNotes
topic_hdr_guidThe topic record’s GUID, not the topic code
urlMaximum 255 characters. Not validated — a typo produces a subscription that silently never delivers.
auth_header_nameA valid HTTP header name. No spaces.
auth_header_valueThe shared secret. Maximum 255 characters.
statusACTIVE. Only DELETED stops delivery; there is no pause.
Create subscriptions through the API, not through the product. The Web Hook screen offers three fields — a title, a URL and a topic — and the field labelled Web Hook Title is stored in the auth_header_name column. It never sets auth_header_value, and BigLedger only sends the header when both halves are present. A subscription created in the user interface therefore arrives at your endpoint with no authentication header at all. The screen also makes the URL read-only once saved, so changing a destination means deleting and recreating. The API sets all four fields properly.

Managing subscriptions:

CreatePOST /core2/tnt/dm/webhook-subscription
UpdatePUT /core2/tnt/dm/webhook-subscription
DeleteDELETE /core2/tnt/dm/webhook-subscription/{guid}
ListGET /core2/tnt/dm/webhook-subscription
Read oneGET /core2/tnt/dm/webhook-subscription/{guid}
FilterGET /core2/tnt/dm/webhook-subscription/query

These need the tenant permissions API_TNT_DM_WEBHOOK_SUBSCRIPTION_CREATE, _UPDATE, _DELETE and _READ, or tenant owner or administrator rank. Step 1 additionally needs API_TNT_DM_WEBHOOK_TOPIC_READ to list topics and API_TNT_DM_WEBHOOK_TOPIC_CREATE to seed or create them. Ask for all of them when you ask for the integration user — a key that can read records cannot manage subscriptions unless it was granted these too.

auth_header_value is stored and returned in clear text, and it is a filterable query parameter. Anyone who can read subscriptions in the tenant can read the secret. Use a value that is worth nothing except to your endpoint, and rotate it on the same schedule as your access key.

What success looks like

Thirty seconds, and worth spending before you write any receiving code.

  1. Point the subscription at a request-capture service (https://webhook.site/… or your own nc -l 8080) instead of your real endpoint.
  2. Make the change the topic names — for CUSTOMER_CREATED, create one customer in the tenant.
  3. Within a second or two you should see one POST, Content-Type: application/json, carrying the header name and value you set, and a body whose top-level key is a table name such as bl_fi_mst_entity_hdr.

If nothing arrives, work backwards in this order: the topic record exists and its topic_code matches the platform’s string exactly; the subscription’s status is ACTIVE; the url has no typo (it is not validated, so a wrong one fails silently and forever); the topic is one the platform actually emits, not merely one populate-default seeded. If the POST arrives but has no authentication header, the subscription was created in the Web Hook screen rather than through the API — delete it and create it again with both header halves.

What arrives at your endpoint

POST /blg/sales-invoice-created HTTP/1.1
Host: hooks.gadgetsphere.example
Content-Type: application/json
X-GadgetSphere-Token: <your auth_header_value>

{ "bl_fi_generic_doc_hdr": {  }, "bl_fi_generic_doc_line": [  ] }

Always POST. Always application/json. The method is not configurable.

The body is the record itself, in the same container shape the Data API returns — and nothing else.

There is no envelope. The payload carries no topic name, no event identifier, no timestamp, no tenant code and no attempt number. Your endpoint knows which event it is only from which URL it was configured on, so give every subscription its own path. And do not assume the body is always an object: several delete topics send the JSON literal true, and a couple send an English sentence with Content-Type: application/json. Parse defensively.

Your endpoint should return quickly — acknowledge, queue, and process asynchronously. BigLedger allows 60 seconds to establish the connection and then waits indefinitely for your response, and delivery for the whole platform runs on a small shared pool. An endpoint that accepts a connection and then hangs is worse than one that fails fast.

Topics

POST /core2/tnt/dm/webhook-topic/populate-default seeds 54 topics. The ones most integrations use:

AreaTopics
EntitiesCUSTOMER_CREATED · CUSTOMER_UPDATED · CUSTOMER_DELETED · SUPPLIER_CREATED · SUPPLIER_UPDATED · SUPPLIER_DELETED · EMPLOYEE_CREATED · EMPLOYEE_UPDATED · EMPLOYEE_DELETED
ItemsFINANCIAL_ITEM_CREATED · FINANCIAL_ITEM_UPDATED · FINANCIAL_ITEM_DELETED · INVENTORY_UPDATED
Sales documentsINTERNAL_SALES_ORDER_CREATED · INTERNAL_SALES_ORDER_UPDATED · INTERNAL_SALES_ORDER_DELETED · INTERNAL_SALES_INVOICE_CREATED · INTERNAL_SALES_RETURN_CREATED · INTERNAL_SALES_RETURN_UPDATED · INTERNAL_SALES_RETURN_DELETED
OrganisationCOMPANY_CREATED · COMPANY_UPDATED · BRANCH_CREATED · BRANCH_UPDATED · LOCATION_CREATED · LOCATION_UPDATED · LOCATION_DELETED
MembershipMEMBERSHIP_CARD_CREATED · MEMBERSHIP_CARD_UPDATED · MEMBERSHIP_CARD_DELETED · MEMBERSHIP_CLASS_CREATED · MEMBERSHIP_POINTS_TXN_CREATED
OtherVOUCHER_CREATED · VOUCHER_UPDATED · VOUCHER_DELETED · INQUIRY_CREATED · INQUIRY_UPDATED · INQUIRY_DELETED · PAYMENT_CONFIG_CREATED · PAYMENT_CONFIG_UPDATED · PAYMENT_CONFIG_DELETED

Some seeded topics never fire, because the code path they name does not emit them — the supplier sales-order and supplier sales-return topics are the clearest example. And document topics follow the pattern <SERVER_DOC_TYPE>_CREATED / _UPDATED / _DELETED, so document types beyond the seeded handful do emit events; their topic records just have to be created by hand.

Before you build on a topic, test it. Subscribe, make the change in the tenant, and confirm your endpoint is called. A topic that is registered but never fires looks identical to a quiet system.

How to build on this

The pattern that works:

  1. Subscribe to the topics you care about, one URL each.
  2. Treat every delivery as “something may have changed” — take the GUID from the body, and re-read the record through …/etl-ep/{guid} before acting.
  3. Reconcile on a schedule with an updated_date_from pull. This catches everything the webhook dropped, and it is the part you must not skip.
  4. Log every delivery on your side, with the raw body. The platform’s own log is not readable.
  5. Return 200 fast. Queue the work.

The webhook makes your integration responsive. The reconciliation pull makes it correct. You need both.

Related documentation

Last updated on