§ Webhooks · Delivery
Delivery contract and callback headers
Each callback is an HTTPS POST of signed JSON to the callback URL configured on your integrator.
| Field | Type | Description |
|---|---|---|
| x-nuvouch-signature | header | HMAC-SHA256 of the raw body, formatted as sha256=<hex>. |
| x-nuvouch-delivery | header | Stable delivery id reused across retries for the same logical callback. |
§ Webhooks · Events
Approval callback events
| Field | Type | Description |
|---|---|---|
| nuvouch.approval_request.approved | event | Approval request was approved. |
| nuvouch.approval_request.denied | event | Approval request was denied. |
| nuvouch.approval_request.expired | event | No decision before expiry. |
| nuvouch.approval_request.cancelled | event | Integrator cancelled pending request. |
approved-event.json
{
"event": "nuvouch.approval_request.approved",
"payload": {
"approvalRequestId": "req_2f5cd6dbb6784b9ab2f7",
"externalRequestId": "payment_auth_001",
"targetUserId": "usr_nuvouch_456",
"status": "approved",
"decisionAt": "2026-04-21T16:14:11.000Z",
"decisionMethod": "biometric"
},
"timestamp": "2026-04-21T16:14:12.000Z"
}§ Webhooks · Linking
Connection lifecycle events
| Field | Type | Description |
|---|---|---|
| nuvouch.connection.accepted | event | A subject tuple was linked to a Nuvouch user. |
| nuvouch.connection.revoked | event | A previously linked subject tuple was revoked. |
accepted-connection.json
{
"eventType": "nuvouch.connection.accepted",
"connectionId": "conn_123",
"connectionSessionId": "conn_sess_123",
"nuvouchUserId": "usr_nuvouch_456",
"subjectId": "cus_123",
"subjectLabel": "Ada Lovelace",
"contextKey": "merchant:acct_live_001",
"contextType": "merchant",
"contextLabel": "Stripe Live Account",
"connectedAt": "2026-04-21T16:05:00.000Z"
}§ Webhooks · Parsing
Parse approval and connection payloads correctly
Parse callback body
sdk helper
import { parseNuvouchWebhookJson } from "@nuvouch/integrator-sdk";
const event = parseNuvouchWebhookJson(rawBody);
if (event.kind === "approval") {
console.log(event.body.event);
console.log(event.body.payload.externalRequestId);
}
if (event.kind === "connection") {
console.log(event.body.eventType);
console.log(event.body.connectionId);
}§ Webhooks · Security
Verify signature before processing
Each delivery includes x-nuvouch-signature and x-nuvouch-delivery. Verify signature over raw request body before parsing business fields.
Verify webhook signature
recommended
import { verifyNuvouchWebhookSignatureFromEnv } from "@nuvouch/integrator-sdk";
const valid = await verifyNuvouchWebhookSignatureFromEnv({
rawBody,
signatureHeader: req.headers.get("x-nuvouch-signature"),
});secret-manager.ts
sdk without envimport { verifyNuvouchWebhookSignature } from "@nuvouch/integrator-sdk";
const callbackSecret = await secrets.get("integrations/nuvouch/callback-secret");
const valid = await verifyNuvouchWebhookSignature({
secret: callbackSecret,
rawBody,
signatureHeader: req.headers.get("x-nuvouch-signature"),
});§ Webhooks · Idempotency
Deduplicate delivery attempts
The x-nuvouch-delivery header is stable across retries for the same logical callback. Persist processed delivery IDs and short-circuit duplicates.
dedupe.ts
import { parseNuvouchWebhookJson } from "@nuvouch/integrator-sdk";
const deliveryId = req.headers.get("x-nuvouch-delivery");
if (deliveryId && await hasProcessed(deliveryId)) {
return new Response("duplicate", { status: 200 });
}
const event = parseNuvouchWebhookJson(rawBody);
await processEvent(event);
if (deliveryId) await markProcessed(deliveryId);§ Webhooks · Retries
Retry model and handler expectations
Delivery behavior
- Any non-2xx response is treated as a failed attempt.
- Timeouts and transport errors are also treated as failures and retried automatically.
- Retry delays are approximately 30s, 120s, 480s, 1800s, and 7200s.
- A 2xx response marks the delivery as succeeded and stops further retries.
- After the final failed attempt, the delivery is marked dead.
Handler guidance
- Verify signature, dedupe, then enqueue async business processing.
- Return 2xx quickly once event is safely persisted.
- Design side effects to be idempotent by both business request id and delivery id.
- Use signed callbacks, not status polling, to trigger the sensitive side effect.
For a complete payment authorization walkthrough, see integration guide.