UPP Docs

Webhooks

Receive status changes and verify signatures.

UPP sends a POST to each enabled webhook endpoint subscribed to an event. Add endpoints under Settings → Webhook endpoints; each endpoint has its own signing secret and can subscribe to specific event types (all by default). Past deliveries are under Settings → Webhook deliveries.

Payload

{
  "id": "evt_...",
  "type": "card_funding.status_changed",
  "created_at": "2026-10-29T08:00:00Z",
  "data": { "id": "cfd_...", "status": "succeeded" }
}
  • type is <resource>.status_changed for status changes, sent only when the status visible to you (data.status) changes. Other events are <resource>.<past tense>, such as withdrawal.rfi_requested.
  • data has the same shape as GET /<resource>/{id}, with status set to the status that triggered the event.
  • Events can arrive out of order. When you need the current state, call the GET endpoint.

All event types and their payloads are listed in the API reference.

Verify the signature

Each request has X-UPP-Signature: t=<unix seconds>,v1=<signature>, where the signature is hex(HMAC-SHA256(secret, "<t>.<raw body>")).

Verify against the raw request body, before parsing JSON, and reject requests whose t is too far from your clock.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyUppSignature(rawBody: string, header: string, secret: string, toleranceSeconds = 300): boolean {
  const parts = Object.fromEntries(header.split(',').map((part) => part.split('=', 2)));
  const timestamp = Number(parts.t);

  if (!parts.v1 || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) {
    return false;
  }

  const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');

  return expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Deduplicate

X-UPP-Event-Id equals the body id and stays the same across retries. Store processed IDs and skip repeats.

Retries

Any 2xx response is a success. Other responses, timeouts (10 seconds) and network errors are retried, up to 6 attempts in total, after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours.

Respond quickly with 2xx and process the event in the background.

On this page