Webhooks
Topics, signature verification, retries, and loop prevention.
Subscribing to a topic
Declare topics in your manifest's webhooks array (see
Publishing & Manifest):
webhooks = ["order.created", "product.updated"]At install, register your endpoint — Prebit posts every subscribed event
to your installation's webhookEndpointUrl.
Topic → domain event mapping
| Manifest topic | Domain event |
|---|---|
order.created | commerce.order.created.v1 |
order.paid | commerce.order.paid.v1 |
refund.created | commerce.refund.created.v1 |
customer.created | commerce.customer.created.v1 |
product.updated | catalog.product.updated.v1 |
collection.updated | catalog.collection.updated.v1 |
inventory.updated | inventory.updated.v1 |
checkout.started | commerce.checkout.started.v1 |
No product.deleted / collection.deleted topic exists yet — deletes
don't notify subscribed apps. Deliberately deferred (see
Products), not silently dropped.
Verifying a delivery
Every delivery carries these headers:
| Header | Meaning |
|---|---|
X-Prebit-Delivery-Id | Unique per delivery attempt — stable across retries of the same attempt |
X-Prebit-Event-Id | The producer event's own id |
X-Prebit-Timestamp | Unix ms, part of the signed payload |
X-Prebit-Signature | HMAC-SHA256(webhookSecret, "${timestamp}.${rawBody}"), hex |
import { verifyPrebitWebhookSignature } from "@prebit/sdk";
const valid = verifyPrebitWebhookSignature({
webhookSecret, // your installation's own secret, shown at registration
timestamp: req.headers["x-prebit-timestamp"],
rawBody, // the EXACT raw request body — parse AFTER verifying, not before
signatureHex: req.headers["x-prebit-signature"],
});
if (!valid) return res.status(401).end();Always fail closed
A signature is rejected if it's missing, malformed, mismatched, or older than 5 minutes — the timestamp is part of what's signed specifically so a captured-and-replayed-later payload fails even with a valid signature for its original moment.
Payload shape
{
"deliveryId": "...",
"eventId": "...",
"eventName": "catalog.product.updated.v1",
"schemaVersion": 1,
"occurredAt": "2026-07-23T12:00:00.000Z",
"storeId": "...",
"aggregate": { "type": "product", "id": "..." }
}The payload is intentionally thin — a pointer (aggregate.type/.id), not
a full resource snapshot. Fetch the current state via the relevant list
endpoint if you need more than the pointer.
Retries and delivery guarantees
Delivery is retried with backoff on failure (network error, non-2xx, or a
redirect — redirects are never followed). A delivery ID is stable across
retry attempts of the same enqueued row, so your own receiver-side
dedup can key on deliveryId safely.
Loop prevention
If your installation's own write caused an event (e.g. you called
createProduct), that delivery is skipped for your installation only
— you never get an echo of your own write. Every other app subscribed to
the same topic still receives it normally. There's no way to opt into
receiving your own echo; if you need to know your own write succeeded, use
the API response itself, not a webhook.
Endpoint requirements
Your webhookEndpointUrl must be an exact HTTPS URL. Redirects are
refused. The endpoint is re-validated against SSRF/DNS-rebinding
immediately before every send, not just at registration time.
A full working example
prebit-examples/webhook-listener is a minimal HTTP
server doing exactly the verification above, with nothing else in the way.