Webhooks

Signed, at-least-once, deduplicated by id.

Status: events flow between matgrk services and payment gateways today. Subscribing your own endpoint is not open yet — what follows is the envelope and signature scheme you will integrate against when it opens.

The envelope

Every event is one JSON object. id is unique per event and is the deduplication key; family groups related types (order, payment, subscription, gateway); data carries the type’s payload.

{
  "id": "01J8…",
  "version": 1,
  "family": "order",
  "type": "order.placed",
  "occurred_at": "2026-09-26T02:00:00Z",
  "tenant_id": "01HZ…",
  "data": { … }
}

Signatures

Every delivery is signed: an HMAC-SHA256 of the raw request body, hex-encoded, in X-Commerce-Signature for order events and X-Pay-Signature for payment events, as sha256=<hex>. An unsigned body is never sent. Verify by recomputing over the exact bytes received — before parsing the JSON — and comparing in constant time.

Verification, in pseudo-code
signature = request.headers["X-Commerce-Signature"]  // "sha256=<hex>"
expected  = "sha256=" + hmac_sha256_hex(secret, raw_request_body)
valid     = constant_time_equal(signature, expected)

Delivery

  • At-least-once: the same event can arrive twice. Deduplicate on id.
  • A failed delivery is retried with exponential backoff, capped at one hour between attempts.
  • Answer with a 2xx quickly and do the work afterwards; a slow handler reads as a failure and is retried.

Subscriptions

Registering your own endpoint — choose the event families you want, receive them at your URL, verify with a per-subscription secret — is part of the planned public webhooks release. There is no subscription endpoint yet; when it ships it will be documented here, and the envelope and signature scheme above are what your handler will already be written against.

Order event types

  • order.placed
  • order.payment_state_changed
  • order.fulfilment_state_changed
  • order.return_state_changed
  • order.cancelled