Prebit Developer Docs

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 topicDomain event
order.createdcommerce.order.created.v1
order.paidcommerce.order.paid.v1
refund.createdcommerce.refund.created.v1
customer.createdcommerce.customer.created.v1
product.updatedcatalog.product.updated.v1
collection.updatedcatalog.collection.updated.v1
inventory.updatedinventory.updated.v1
checkout.startedcommerce.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:

HeaderMeaning
X-Prebit-Delivery-IdUnique per delivery attempt — stable across retries of the same attempt
X-Prebit-Event-IdThe producer event's own id
X-Prebit-TimestampUnix ms, part of the signed payload
X-Prebit-SignatureHMAC-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.

On this page