For developers

Sakb for developers

Sakb exposes no internal APIs. The system has exactly two doors — an inbox that receives, an outbox that notifies — and this guide teaches you how to shape your payload so the right door deals with it.

Last updated · 11 Aug 2026Version · 1.0

The two doors

You never call Sakb functions. You describe what happened on your side in an envelope, and Sakb decides how it becomes a correct document in the books.

  • Inbox — your system sends a signed envelope to your integration's endpoint. Sakb verifies it and your integration's recipe turns it into a document awaiting the accountant's review. There is no endpoint per document — one address, and a message type inside the envelope.
  • Outbox — Sakb pushes signed events to your addresses (an invoice issued, a receipt collected…). You subscribe to events and verify each delivery's signature — no polling.

Your integration's own endpoint, key and signing secret live in-product: Settings → Integrations.

The envelope — shaping your payload

Every submission is an envelope plus a payload. The envelope carries the message's identity and routing; the payload is your data, in whatever shape your recipe agrees on:

POST https://api.sakb.sa/api/public/v1/inbound/custom
Authorization: Bearer sakb_gk_…
X-Sakb-Signature: sha256=…
X-Sakb-Timestamp: 2026-08-11T09:30:00Z

{
  "envelope": {
    "messageType": "sales.invoice.received",
    "version": "1.0",
    "idempotencyKey": "inv-10452-delivery-1",
    "occurredAt": "2026-08-11T09:30:00Z",
    "source": { "channel": "custom", "externalId": "INV-10452" }
  },
  "payload": { }
}
  • messageType stream.event — the last segment is the event, the rest the stream; your recipe matches on both.
  • idempotencyKey unique per delivery: a resend with the same key and content answers the same result and never creates a second record.
  • source.externalId your document's number on your side — how Sakb recognizes the same document if it arrives again.
  • payload your data — the recipe inside Sakb maps it onto the document's fields.

Signing

Every inbound request is signed with your integration's signing secret and carried in the X-Sakb-Signature and X-Sakb-Timestamp headers (±5-minute window):

base      = rawBodyUtf8 + "\n" + X-Sakb-Timestamp
signature = "sha256=" + hex(HMAC-SHA256(base, signingSecret))

Anything unsigned, mis-signed or stale is refused — there is no pass-without-verification path.

Message status

Poll GET /api/public/v1/messages/:id for the outcome:

  • PROCESSED it became a document — the identifiers ride the answer.
  • NEEDS_USER it arrived and stopped for the accountant's review inside Sakb — not necessarily your error.
  • REJECTED refused — the reason rides the message record.

Outbox — webhooks

Subscribe with your address and your events, then verify every delivery's signature with the subscription's secret.

On secret rotation the old secret stays valid for 7 days — Sakb signs with both during the window.

Sandbox mode

Send the same envelope with "sandbox": true — it runs the full pipeline and stops before writing, so you see the outcome as a preview with no trace in the books.

We use necessary cookies and local storage to remember your language and preferences, in line with the Saudi Personal Data Protection Law (PDPL). Privacy policy