Webhooks — Provider Registration¶
Overview¶
External POS systems register a callback URL to receive event notifications from Cata. Registration is per-provider per-store — each outlet can have a different webhook endpoint for each POS provider.
When an event fires (e.g. order.paid), Cata dispatches the payload to the registered URL with HMAC signature verification.
Available Events¶
| Event | Description | Status |
|---|---|---|
order.paid |
Order is paid and ready for fulfillment | V1 |
order.status_updated |
Order status changed (accepted, preparing, ready, etc.) | Future |
order.cancelled |
Order was cancelled | Future |
menu.updated |
Menu was published/changed | Future |
Discovery Endpoint¶
GET /api/v1/webhooks/events
Returns the list of events that Cata supports. No authentication needed.
{
"events": [
{ "name": "order.paid", "description": "Triggered when an order is paid and ready for fulfillment" }
]
}
Registration¶
Register a Webhook¶
POST /api/v1/webhooks/register
Request:
{
"outletId": "store-uuid-123",
"provider": "revel",
"callbackUrl": "https://revel.example.com/cata/orders",
"events": ["order.paid"]
}
Response (201):
{
"code": 201,
"isSuccess": true,
"message": "webhook registered",
"webhook": {
"id": 1,
"outletId": "store-uuid-123",
"provider": "revel",
"callbackUrl": "https://revel.example.com/cata/orders",
"events": ["order.paid"],
"secret": "whsec_abc123...",
"isActive": true,
"createdAt": "2026-03-13T10:00:00Z"
}
}
Secret is shown only once
The secret field is returned only on creation and on secret rotation. It cannot be retrieved later. Store it securely — you need it to verify incoming webhook signatures.
Management Endpoints¶
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/v1/webhooks |
List all webhooks for tenant |
GET |
/api/v1/webhooks/{webhookId} |
Get webhook detail (no secret) |
PUT |
/api/v1/webhooks/{webhookId} |
Update callbackUrl, events, isActive |
DELETE |
/api/v1/webhooks/{webhookId} |
Soft delete webhook |
POST |
/api/v1/webhooks/{webhookId}/rotate-secret |
Generate new secret (returns it once) |
Data Model¶
store_webhooks table¶
| Column | Type | Description |
|---|---|---|
store_id |
BIGINT | FK to stores — which outlet this webhook belongs to |
provider |
VARCHAR(50) | Provider identifier (e.g. "revel", "deliverect") |
callback_url |
VARCHAR(2048) | Where Cata POSTs events |
secret |
VARCHAR(255) | HMAC secret for signing outbound payloads |
events |
JSON | Array of subscribed events (e.g. ["order.paid"]) |
is_active |
TINYINT(1) | Whether this webhook is active |
Unique constraint: one webhook per (store_id, provider).
Why separate from store_credentials? Credentials are for Cata calling into the external POS API (e.g. fetching menus). Webhooks are for Cata calling out to the external POS's listener (e.g. dispatching orders). Different concern, different lifecycle.
Signature Verification¶
Every outbound webhook POST from Cata includes these headers:
| Header | Value |
|---|---|
X-Cata-Signature |
sha256=<hex(HMAC-SHA256(secret, body))> |
X-Cata-Event |
Event name (e.g. order.paid) |
X-Cata-Delivery-ID |
Unique delivery UUID (idempotency key) |
X-Cata-Timestamp |
Unix timestamp of the dispatch |
Verification Example (pseudocode)¶
import hmac, hashlib
def verify_signature(secret, body, signature_header):
expected = "sha256=" + hmac.new(
secret.encode(),
body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
Event Envelope¶
Every webhook payload uses this standard envelope:
{
"eventId": "evt_01HXYZ123ABC",
"eventType": "order.paid",
"occurredAt": "2026-03-13T10:00:00Z",
"tenantId": "tenant_123",
"outletId": "store-uuid-123",
"payload": {
"id": "order-uuid-456",
"items": [...],
"customer": {...},
"total": { "amount": 2500, "currency": "AED" }
}
}