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.
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.