Skip to content

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" }
  }
}