Delivery

Customer webhooks

Receive signed spread, splash and funding events at one public HTTPS endpoint.

Builder+ Configure the endpoint from Profile → Developer. The signing secret is shown only when the endpoint is created or rotated.

Production availability

Customer delivery is live for Builder and Pro. Configure and verify the endpoint from Profile → Developer; Free correctly rejects webhook configuration.

Endpoint verification

Umbra sends a signed challenge before normal delivery starts. Return a2xx JSON response containing the exact challenge: { "challenge": "<same value>" }.

json
{
  "id": "verify_<revision>",
  "type": "umbra.webhook.verify",
  "created_at": 1784419200,
  "data": { "challenge": "<random challenge>" }
}

Verify every signature

Each request includes these headers:

  • X-Umbra-Delivery — stable delivery identifier.
  • X-Umbra-Timestamp — Unix seconds used by the signature.
  • X-Umbra-Signature v1=<HMAC-SHA256 hex>.

Compute HMAC-SHA256 over<timestamp>.<delivery_id>.<exact raw request body>. Compare in constant time before parsing JSON, reject old timestamps, and deduplicate by delivery ID.

typescript
import crypto from "node:crypto";

function verifyUmbra(req, rawBody, secret) {
  const timestamp = req.headers["x-umbra-timestamp"];
  const delivery = req.headers["x-umbra-delivery"];
  const supplied = req.headers["x-umbra-signature"]?.replace(/^v1=/, "");
  if (!/^[a-f0-9]{64}$/i.test(supplied ?? "")) return false;
  const signed = Buffer.concat([
    Buffer.from(timestamp + "." + delivery + "."),
    rawBody,
  ]);
  const expected = crypto.createHmac("sha256", secret).update(signed).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(supplied, "hex"),
    Buffer.from(expected, "hex"),
  );
}

Delivery envelope

json
{
  "id": "wh_<delivery id>",
  "type": "umbra.spread",
  "created_at": 1784419200,
  "data": { "...": "the canonical alerts.spread.v1 event" }
}

type is umbra.spread,umbra.splash, or umbra.funding. Thedata object is the canonical family event; consumers should ignore unknown additive fields.

Builder and Pro behavior

  • Builder — spread opens, splash events, and funding events; 60 deliveries/minute.
  • Pro — selectable families, spread closes, symbol/exchange/minimum-magnitude filters, 600 deliveries/minute, and replay.

Retries and response handling

Any 2xx is success. Network failures, 408,425, 429, and 5xx responses retry after 1, 5, 30, 120, then 600 seconds, up to six total attempts. Other 4xx responses dead-letter immediately. Redirects are never followed.

Respond quickly

Authenticate, enqueue, and return 2xx. Perform expensive processing asynchronously so retries do not create avoidable duplicates.

Replay

Pro users can replay retained failed deliveries from the Developer panel when the endpoint's current family, event, and filter selection still permits the event. A replay receives a new delivery ID and carries the original event payload.