POST/v1/webhooks
Webhooks
Subscribe a URL to events such as sale.created, customer.created, online order updates and stock.low. Every delivery is signed with HMAC-SHA256 so you can verify it came from Nafaa, and a sample endpoint returns real example payloads for tools like Zapier.
Updated
Subscribe with POST /v1/webhooks and a body of { "url": "https://example.com/hooks/nafaa", "events": ["sale.created"] }. GET /v1/webhooks/events lists every event, GET /v1/webhooks/sample/{type} returns an example payload, and DELETE /v1/webhooks/{id} unsubscribes.
Events
sale.created, customer.created, online_order.created, online_order.accepted, online_order.dispatched, online_order.delivered, online_order.cancelled, online_order.returned and stock.low.
Verifying a delivery
Each request carries X-Nafaa-Event, X-Nafaa-Delivery (the event id; ignore repeats), X-Nafaa-Timestamp and X-Nafaa-Signature: t=<timestamp>,v1=<hex>. The signature is an HMAC-SHA256 of <timestamp>.<raw body> with your webhook secret.
Verify the signature
import crypto from 'node:crypto';
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split('=', 1) for p in header.split(','))
signed = parts['t'].encode() + b'.' + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts['v1'])