Skip to main content
Results are delivered only by webhook. There’s no polling for outcomes: when a step verifies or a session concludes, UIP POSTs a signed event to your configured URL. Set the URL in your dashboard before creating sessions.

Events

EventWhenKey payload
step.completedA step verified successfullystage, primitive, and claims / satisfied / signature
session.completedAll steps donesession_id, status
session.stoppedYou ended it at a gatesession_id, reason
session.failedA step failed, funds ran out, or an internal errorsession_id, reason
Every delivery also echoes your client_reference_id and any metadata. See the event reference for full payloads.

Delivery guarantees

Ordered per step

step.completed fires in step order (stage 0, 1, 2…), followed by the terminal session.* event.

At-least-once

Delivery is retried with backoff until it succeeds. Each event has a stable X-UIP-Delivery-Iddedupe on it so a retry is harmless.

Off the request path

Webhooks are queued and sent out-of-band, so a slow endpoint never blocks verification — but it does delay your delivery.

Fired after the result is durable

An event is only sent once the verified result + audit record are committed, so a webhook you receive always reflects persisted state.

Verifying signatures

Every delivery is signed with your business’s webhook secret (uip_whsec_... from the dashboard). Always verify before trusting a payload. Each request carries:
HeaderValue
X-UIP-EventThe event name
X-UIP-TimestampUnix seconds when signed
X-UIP-SignatureHMAC-SHA256(secret, "{timestamp}.{raw_body}"), hex
X-UIP-Delivery-IdStable per-event id (dedupe key)
Recompute the HMAC over timestamp + "." + raw_request_body and compare in constant time. Use the raw bytes — re-serializing the JSON can change them and break the match.
import crypto from "node:crypto";

// mount with: express.raw({ type: "application/json" })
app.post("/uip-webhook", (req, res) => {
  const ts = req.get("X-UIP-Timestamp");
  const sig = req.get("X-UIP-Signature");
  const expected = crypto
    .createHmac("sha256", process.env.UIP_WEBHOOK_SECRET)
    .update(`${ts}.${req.body}`) // req.body is a Buffer (raw)
    .digest("hex");

  const ok =
    sig &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  if (!ok) return res.sendStatus(401);

  // Optional: reject stale timestamps to limit replay windows.
  const event = JSON.parse(req.body.toString());
  // ...handle event, dedupe on X-UIP-Delivery-Id...
  res.sendStatus(200);
});
Respond 2xx quickly and do heavy work asynchronously. A non-2xx (or timeout) is treated as a failed delivery and retried.

Rotating the secret

Generate or rotate your webhook secret from the dashboard. Signatures are computed with the secret snapshotted at delivery time, so rotate, then update your verifier.