Skip to main content
This walks you from zero to a verified webhook in four steps. The example runs an anonymous age check (age_verify), but the flow is identical for any primitive.
1

Get your API key + set a webhook

Sign up at uip.digital, create a business, and from the dashboard copy your API key (uip_...) and set your webhook URL.
A webhook URL is required. Results are delivered only by webhook — there’s no polling for outcomes — so a session won’t be created until your business has a webhook configured. You’ll also need your webhook signing secret (uip_whsec_...) from the dashboard to verify deliveries (step 4).
2

Create a session

Server-to-server, with your API key. List the steps you want verified.
curl https://api.uip.digital/v1/sessions \
  -H "Authorization: Bearer $UIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "steps": [ { "primitive": "age_verify", "params": { "min_age": 21 } } ],
    "client_reference_id": "order_4815"
  }'
UIP returns the session and its hosted URL:
{
  "id": "sess_abc123",
  "status": "created",
  "url": "https://uip.digital/s/sess_abc123",
  "expires": "2026-06-07T12:34:56Z"
}
3

Send the user to the hosted page

Hand the user session.url. On the same device, link to it directly; on a different device (e.g. your desktop checkout → their phone), render it as a QR code. UIP’s hosted page drives the user’s wallet over the W3C Digital Credentials API — you host nothing and never touch the wallet.
4

Receive the signed result

When the wallet responds and UIP verifies it, you get a POST to your webhook URL — one step.completed per step, then a terminal session.* event.
POST /your-webhook
X-UIP-Event: step.completed
X-UIP-Timestamp: 1749300000
X-UIP-Signature: 9f86d081884c7d65...
X-UIP-Delivery-Id: sess_abc123:step.completed:0

{
  "event": "step.completed",
  "data": {
    "session_id": "sess_abc123",
    "client_reference_id": "order_4815",
    "stage": 0,
    "primitive": "age_verify",
    "satisfied": true
  }
}
Always verify the signature before trusting a delivery:
import crypto from "node:crypto";

function verify(req, secret) {
  const ts = req.headers["x-uip-timestamp"];
  const sig = req.headers["x-uip-signature"];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${req.rawBody}`) // raw bytes, not re-serialized JSON
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}

Compose multiple steps

Real flows often need more than one verified action — for example, identify the signer, then sign the contract. List them in order; the user completes them in one session and you receive one webhook per step, in sequence.
{
  "steps": [
    { "primitive": "identify", "params": { "blocks": ["first_name", "last_name", "country"] } },
    { "primitive": "sign", "params": { "document_ids": ["contract"] } }
  ]
}
sign carries documents, so its session is created as multipart/form-data — see Primitives → sign and Create a session.

Test without a phone

In development you can drive the full pipeline (verify → webhook) from the hosted page’s Simulate button — no wallet required. Real wallet verification runs in both demo and production; see Wallets.

Next steps

Primitives

Every primitive’s parameters and result shape.

Sessions

Lifecycle, gates (continue/stop), binding, and expiry.

Webhooks

Every event, payload, and signature verification.

Create a session

The full request reference, including all options.