← Back to product·Simulmedia Switchboard Docs · v1 draft·Operate / Webhooks
Operate

Webhooks

Webhooks are fast hints, not the record of truth. The contract is deliberately boring: hints arrive fast, truth is pullable, and nothing depends on your uptime. Design accordingly, on either side of the marketplace.

  • Every event shares one envelope: {event_type, order_id, external_order_id, seq, status, substatus, units?, artifact?, error?, occurred_at}, with a per-order, strictly increasing seq. Retries reuse the same seq. See the event envelope.
  • Both directions. Buyers receive order lifecycle events; sellers receive order.needs_review, order.canceled, negotiation.offer, and catalog.integrity_failed. Same envelope, same rules. See the event types.
  • Verification: every delivery is HMAC-signed over (timestamp, body) in X-Signature; verify before trusting, reject stale timestamps.
  • Delivery is best-effort: 3 attempts with exponential backoff, roughly 25 seconds worst case, then log-and-drop. An outage on your side longer than that misses events, by design; recovery is built in, below.
  • No public endpoint? Use a queue. Switchboard can deliver events to a shared SQS queue with AWS-native access control, first-class alongside webhooks. Timestamps are UTC everywhere.

The receiver, correctly

Four duties, in order: verify the signature, acknowledge with a 2xx fast, process async, and dedup on (order_id, seq). Return 204 for duplicates so retries stop.

app.post("/webhooks/switchboard", (req, res) => {
  verifyHmac(req.headers["x-signature"], req.body);   // reject stale timestamps
  const { order_id, seq } = req.body;
  if (seen(order_id, seq)) return res.status(204).end();  // dedup: retries reuse seq
  enqueue(req.body);                                   // ack fast, process async
  res.status(200).end();
});

Recovery, always available

  • Missed a window? GET /v1/events?order={id}&after_seq={n} replays everything after your high-water mark.
  • Not sure where you are? GET /v1/orders/{id} returns the snapshot with last_seq; reconcile against it, then replay the gap.
  • After every deploy or outage: sweep GET /v1/orders?status=submitted,validating,needs_confirmation,confirmed,negotiating,seller_review and replay each. Every non-terminal status is in that list; drop needs_confirmation only if you never submit document orders.
  • Long pauses are normal: orders park in seller_review for minutes or hours with no events. A low-frequency safety poll on parked orders costs nothing and closes the last gap.

The sweep is one half of the reconciliation posture; the rest is Reconciliation.

If you cannot expose a public endpoint at all, request shared-queue delivery (SQS) instead: same envelope, same seq semantics, no inbound firewall conversation.