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

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

FieldTypeDescription
url requiredstringHTTPS URL that receives deliveries. Must be on your organization's allowlist; registering a non-allowlisted URL returns 422.
eventsarray of stringFilter to specific event types. Omit to receive every event for your role.
labelstringFree-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

StatusCodeWhen
422INVALID_INPUTurl 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

StatusCodeWhen
404NOT_FOUNDThe 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. 204 on 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.
Deliveries are hints, not truth. The order snapshot is authoritative, and replay closes any gap; nothing depends on your uptime.

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.