Webhooks
Hisab signs and delivers invoice and customer events to your endpoint, with automatic retries. Everything is configured from the dashboard.
Webhooks are managed entirely from the dashboard. No API call is needed to register one.
In the dashboard, open Settings, then Webhooks, and add your HTTPS endpoint.
Pick the events you want to receive and copy the signing secret. You will need it to verify deliveries.
Return a 2xx quickly from your endpoint. Anything else is treated as a failure and retried.
Nine event types are dispatched today, covering the invoice lifecycle and customer changes.
invoice.created | A draft invoice was created |
invoice.updated | A draft invoice was updated |
invoice.finalized | An invoice received its official number |
invoice.sent | An invoice was marked as sent |
invoice.paid | An invoice was marked as paid |
invoice.voided | An invoice was voided |
customer.created | A customer was created |
customer.updated | A customer was updated |
customer.deleted | A customer was archived |
Deliveries are JSON POSTs carrying the event name, the resource snapshot, your organization id and the emission time:
POST https://example.ma/hisab-webhook
Content-Type: application/json
X-Webhook-Signature: v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Webhook-Timestamp: 1780750800
X-Webhook-Event: invoice.paid
X-Webhook-Delivery-Id: del_3f8c2a91
User-Agent: Hisab-Webhooks/1.0
{
"event": "invoice.paid",
"data": {
"id": "inv_8f3a91",
"invoice_number": "FAC-2026-0142",
"status": "paid",
"total": "12480.00"
},
"organization_id": "org_4d11a7",
"created_at": "2026-06-05T14:00:00Z"
}event | string |
data | object |
organization_id | string |
created_at | datetime (ISO 8601) |
Every delivery is signed with your endpoint's secret: HMAC-SHA256 over the timestamp, a dot, and the raw body. Always verify before trusting a payload.
X-Webhook-Signature: v1=HMAC_SHA256(secret, timestamp + "." + payload)import { verifyWebhookSignature } from 'hisab-sdk';
export async function POST(req: Request) {
const payload = await req.text();
const valid = verifyWebhookSignature({
payload,
signature: req.headers.get('x-webhook-signature')!,
timestamp: req.headers.get('x-webhook-timestamp')!,
secret: process.env.HISAB_WEBHOOK_SECRET!,
});
if (!valid) return new Response('Invalid signature', { status: 401 });
return new Response('ok');
}A delivery succeeds on any 2xx response within 30 seconds. Anything else is retried up to 5 times with growing delays:
| Attempt | Delay |
|---|---|
| 1 | ~1 min |
| 2 | ~5 min |
| 3 | ~15 min |
| 4 | ~1 h |
| 5 | ~4 h |
Delays carry about 20% random jitter, so exact times vary. After the fifth failure the delivery is marked failed; you can replay it manually from the dashboard.
Reject anything that fails verification. It did not come from Hisab.
Acknowledge with a 2xx before doing heavy work; the delivery times out after 30 seconds.
Retries reuse the X-Webhook-Delivery-Id header. Store it and skip what you already processed.
Push the event to a queue or a background job instead of processing it inline.
Store it like a password and rotate it from the dashboard if it ever leaks.