Webhooks
Push delivery for events: register a notify URL, verify every signature, and treat every delivery as a hint. Buyers and sellers use the same machinery. Concept and receiver guidance: Webhooks.
Register a notify URL
POST/v1/webhooks/endpointswebhooks:manage
Register an HTTPS endpoint to receive event deliveries.
Request body
| Field | Type | Description |
|---|---|---|
url required | string | HTTPS URL that receives deliveries. Must be on your organization's allowlist; registering a non-allowlisted URL returns 422. |
events | array of string | Filter to specific event types. Omit to receive every event for your role. |
label | string | Free-text name shown in Settings. |
Response
The endpoint starts as pending_verification: the platform sends a signed ping, and verifying its signature confirms the endpoint. signing_key_id names the key that signs your deliveries.
201 Response
{
"endpoint_id": "whe_4c1a",
"url": "https://yourapp.example.com/webhooks/switchboard",
"events": ["order.status_changed", "catalog.stale_rate_card"],
"label": "production-buyer-webhook",
"status": "pending_verification",
"signing_key_id": "key_2026q4"
}
Errors
| Status | Code | When |
|---|---|---|
| 422 | INVALID_INPUT | url is not HTTPS or is not on your organization's allowlist. |
Remove an endpoint
DELETE/v1/webhooks/endpoints/{id}webhooks:manage
Deregister a notify URL. Delivery stops immediately; events keep accruing on your orders and stay replayable.
Response
204 with an empty body.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | The endpoint id does not exist within your organization. |
Verifying the signature
Every delivery is signed: HMAC-SHA256 over the timestamp and the raw body, carried in X-Signature as t={unix_ts},v1={hex_digest}. Verify before trusting the payload, and reject stale timestamps. X-Delivery-Attempt carries 1, 2, or 3. Signing keys rotate; the signing_key_id on your registration names the active key.
// Python import hmac, hashlib, time def verify(secret, header, body): parts = dict(p.split('=',1) for p in header.split(',')) ts, sig = parts['t'], parts['v1'] if abs(time.time() - int(ts)) > 300: raise ValueError("stale timestamp") expected = hmac.new(secret.encode(), f"{ts}.{body}".encode(), hashlib.sha256).hexdigest() return hmac.compare_digest(expected, sig) // Node const crypto = require('crypto'); function verify(secret, header, rawBody) { const {t, v1} = Object.fromEntries(header.split(',').map(p => p.split('='))); if (Math.abs(Date.now()/1000 - +t) > 300) throw new Error('stale'); const expected = crypto.createHmac('sha256', secret) .update(`${t}.${rawBody}`).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)); }
Receiver pattern
Verify, ack fast, process async, dedup on (order_id, seq).
app.post('/webhooks/switchboard', (req, res) => {
verify(process.env.SWB_SIGNING_KEY, req.headers['x-signature'], req.rawBody);
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();
});
After any outage, replay with GET /v1/events?order={id}&after_seq={last_seen}. The full receiver and recovery pattern: Webhooks.
Delivery semantics
- Up to 3 attempts per event, exponential backoff (roughly 5s, then 25s). After that: log and drop; recover via the snapshot plus replay.
- Your endpoint must respond within 10 seconds; slow responses are treated as failures.
- Non-2xx responses trigger a retry.
204on dedup is the correct response; it stops retries. - An allowlisted endpoint that becomes unreachable for more than 1 hour is marked degraded; you receive a platform notification and can re-verify under Settings.
Shared-queue delivery (SQS)
Request a shared SQS queue per counterparty for event delivery without a public endpoint. Same envelope, same seq semantics, AWS-native access control: grant sqs:SendMessage on your queue to the Switchboard service principal and provide the queue ARN under Settings → Delivery.