Subscribe to real-time webhooks
Webhooks let you react to sale.created, purchase_order.received, stock.low, connection.approved, and connection.revoked events instead of polling. They're set up from the developer dashboard, not called with an x-api-key, and delivery runs on a ~5-minute cron sweep rather than instantly.
AskBiz webhooks push five event types to a URL you control: sale.created, purchase_order.received, stock.low, connection.approved, and connection.revoked — no others exist today. Unlike every other page in this API reference, webhooks are not something a third-party server calls with an x-api-key. They’re an account-settings action: you register the endpoint URL and event types from the developer.askbiz.co dashboard’s Webhooks page, the same place you’d manage API keys. Delivery is asynchronous and runs on a cron sweep roughly every 5 minutes, not instantly.
- 1
Create a webhook from the dashboard
Webhooks aren’t created with an x-api-key REST call — they’re managed from the developer.askbiz.co dashboard’s Webhooks page, the same way you’d manage account settings. Add your endpoint URL (must be https://) and pick which event types to subscribe to: sale.created, purchase_order.received, stock.low, connection.approved, connection.revoked. You can subscribe to as few or as many as you like, and you can hold up to 10 webhooks per account.
- 2
Save the whsec_ secret — it’s shown once
Creating a webhook generates a signing secret in the form whsec_… and shows it to you exactly once, at creation time. Store it in your own environment (e.g. ASKBIZ_WEBHOOK_SECRET) immediately — there’s no way to retrieve it again later from the dashboard, only to delete the webhook and create a new one.
- 3
Verify the x-askbiz-signature header on your receiving endpoint
Every delivery is signed: the request body is HMAC-SHA256’d with your webhook secret, and the resulting hex digest is sent in the x-askbiz-signature header. Compute the same HMAC over the raw request body on your end and compare it to the header using a constant-time comparison — never a plain === or ==, which leaks timing information. Reject the request if it doesn’t match. Each delivery’s JSON body is { "event": "…", "data": { … } }, where event is one of your subscribed event types.
const crypto = require('crypto') function isValidSignature(rawBody, signatureHeader, secret) { const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex') // Constant-time comparison — never use === on secrets/signatures return crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected)) } // Example Express handler — use the raw request body, not a re-serialized object app.post('/webhooks/askbiz', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.headers['x-askbiz-signature'] if (!isValidSignature(req.body, signature, process.env.ASKBIZ_WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature') } const { event, data } = JSON.parse(req.body) // event is one of: sale.created | purchase_order.received | stock.low | connection.approved | connection.revoked console.log(event, data) res.status(200).send('ok') }) - 4
Send a test event and check the delivery log before going live
From the webhook’s row on the dashboard, use "Send test event" to enqueue a real synthetic delivery through the exact same signing and delivery path as a live event — it exercises your actual signature-verification code, not a mock. Then use "View deliveries" to see recent delivery attempts and their status. Do this before wiring the webhook into anything that matters, rather than waiting for a real sale, purchase order, or low-stock event to find out your endpoint or secret is wrong.
- 5
Plan around ~5-minute delivery latency, not instant delivery
Webhook delivery isn’t real-time — events are captured immediately when the underlying action happens, but delivery runs on a cron sweep that fires roughly every 5 minutes. Don’t build a flow that assumes sub-second or even sub-minute delivery; if you need a synchronous result (e.g. confirming a scan succeeded before continuing), use the relevant REST endpoint’s direct response instead of waiting on a webhook.
Event types and payload shape
Every delivery body has the shape { "event": "…", "data": { … } }, where event is the event type name and data holds the event-specific fields below.
sale.created
Fires when a POS sale completes.
{
"event": "sale.created",
"data": {
"transaction_id": "b4a1...",
"total": 1450,
"subtotal": 1400,
"tax_amount": 50,
"payment_type": "mpesa",
"created_at": "2026-07-17T09:12:00.000Z"
}
}purchase_order.received
Fires when a purchase order’s status transitions to received.
{
"event": "purchase_order.received",
"data": {
"purchase_order_id": "9f2e...",
"supplier_id": "1c7d...",
"total_cost": 32000,
"received_at": "2026-07-17T09:12:00.000Z"
}
}stock.low
Fires once, at the moment an item’s stock quantity crosses at or below its low-stock threshold — not repeatedly on every update while it stays low.
{
"event": "stock.low",
"data": {
"inventory_id": "e83a...",
"name": "Coca-Cola 500ml",
"stock_qty": 4,
"low_stock_threshold": 10
}
}connection.approved
Fires when a merchant approves a Connection request — or, on a test key, the instant a sandbox fixture connection is created (POST /api/v1/connections with a test key never reaches a real merchant, but still fires this event so you can verify your receiver before going live). test_mode tells the two apart.
{
"event": "connection.approved",
"data": {
"connection_id": "c1a2b3c4-...",
"app_id": "d4e5f6a7-...",
"merchant_email": "owner@example-shop.com",
"scopes": ["read_inventory"],
"test_mode": false,
"approved_at": "2026-07-21T09:12:00.000Z"
}
}connection.revoked
Fires when a merchant revokes a Connection — from the original /connect/{token} confirmation page, or from their own AskBiz account’s Connected Apps settings. This is the only way to find out about a merchant-initiated revoke without polling GET /api/v1/connections.
{
"event": "connection.revoked",
"data": {
"connection_id": "c1a2b3c4-...",
"app_id": "d4e5f6a7-...",
"merchant_email": "owner@example-shop.com",
"revoked_at": "2026-08-02T14:03:00.000Z"
}
}What’s next
For the request-response endpoints that make up the rest of the API, see API Reference. If you haven’t built anything against AskBiz yet, start with the quickstart.
Webhooks FAQ
Can I create or manage webhooks with an API call instead of the dashboard?+
No. Webhook management (create, list, update, delete) is session-authenticated and only available from the developer.askbiz.co dashboard’s Webhooks page — there’s no x-api-key REST endpoint a third-party server calls to register a webhook. It’s treated as an account setting, not a per-request API action.
How fast are webhook deliveries?+
Not instant. Events are captured the moment the underlying action happens, but delivery to your endpoint runs on a cron sweep that fires roughly every 5 minutes, so plan for delivery latency bounded by about 5 minutes rather than real time.
What happens if I lose my webhook secret?+
The whsec_… secret is shown exactly once, at the moment you create the webhook. There’s no way to view it again afterward — if you lose it, delete the webhook and create a new one to get a fresh secret.
Why did signature verification fail even though the secret is correct?+
The most common cause is computing the HMAC over a re-serialized version of the JSON body instead of the exact raw bytes received — re-serializing can change key order or whitespace and produce a different digest. Compute the signature over the raw request body before any JSON parsing.
Can I test a webhook without waiting for a real sale or stock event?+
Yes — use "Send test event" on the webhook’s row in the dashboard. It enqueues a real delivery through the same signing and cron-delivery path as a live event, and "View deliveries" shows you the resulting attempt and status, so you can confirm your endpoint and signature check work before relying on a real event.