diff --git a/api-features/webhooks-events.mdx b/api-features/webhooks-events.mdx
index 2a88a1a..11c80d7 100644
--- a/api-features/webhooks-events.mdx
+++ b/api-features/webhooks-events.mdx
@@ -1,30 +1,31 @@
---
title: "Webhooks & Events"
-description: "Real-time notifications for payment lifecycle events and request status changes"
+description: "Real-time notifications for payments, transaction screening, and hosted onboarding"
---
## Overview
-Webhooks provide real-time notifications when payment and request events occur, enabling immediate response to status changes without constant polling.
+Webhooks provide real-time notifications when payment, transaction-screening, and hosted-onboarding events occur, enabling immediate response to status changes without constant polling. Depending on the event, Request Network can deliver a notification to a platform's Client ID, its linked orchestrator, or both. See the [Webhooks reference](/api-reference/webhooks#current-webhook-events) for recipient routing and the complete technical contract.
## Event Categories
-The platform and Client ID webhook catalog includes **10 event types** across six categories:
+The current webhook catalog includes these events:
| Category | Events |
| --- | --- |
-| **Payment** (core) | `payment.confirmed`, `payment.partial`, `payment.failed`, `payment.refunded` |
-| **Processing** (crypto-to-fiat) | `payment.processing` (with `subStatus`) |
-| **Request** | `request.recurring` |
-| **Compliance / Bank** | `compliance.updated`, `payment_detail.updated` |
+| **Hosted onboarding** | `client_id.linked` |
+| **Payment** | `payment.confirmed`, `payment.failed` |
+| **Transaction screening** | `kyt.screening.completed` |
| **Secure Payment Page** (payer funnel) | `secure_payment.user_event` (with `userEvent`) |
| **Secure Payment** (payer-wallet allowlist) | `secure_payment.access_rejected` |
+For events retained for older API integrations, see [Legacy integrations](/api-reference/webhooks#legacy-integrations).
+
Payment webhook payloads include `payerAddress`, the address used to make the payment, and `payerEoaAddress`, the payer's connected wallet address. These can differ when a smart account is used. Both are `null` when unavailable. See the [Webhooks reference](/api-reference/webhooks) for the full payload schema.
-For full payload schemas and headers, see the [Webhooks reference](/api-reference/webhooks).
+For delivery headers and currently documented payload examples, see the [Webhooks reference](/api-reference/webhooks).
## How It Works
@@ -37,7 +38,7 @@ graph LR
```
**Process:**
-1. **Event occurs:** Payment confirmed, request created, compliance updated
+1. **Event occurs:** Payment confirmed, KYT screening completed, or hosted onboarding finished
2. **Secure delivery:** HMAC SHA-256 signed POST to your configured endpoint
3. **Your processing:** Verify `x-request-network-signature`, update application state
4. **Reliable delivery:** 3 retries (1s, 5s, 15s delays) with 5-second timeout
@@ -54,7 +55,7 @@ graph LR
- **Test webhook identification:** `x-request-network-test` header for development
### Development Tools
-- **Test deliveries:** Fire test events via `POST /v1/webhook/test` (Auth API) — see [Webhooks reference](/api-reference/webhooks#testing)
+- **Test deliveries:** Use the relevant platform or orchestrator test endpoint — see the [Webhooks reference](/api-reference/webhooks)
- **ngrok integration:** Receive webhooks locally during development
- **Comprehensive logging:** Request API logs all delivery failures with attempt details
@@ -62,24 +63,26 @@ graph LR
- **Invoice systems:** Automatically mark invoices as paid when `payment.confirmed` received
- **Order fulfillment:** Release goods or services immediately after payment confirmation
-- **Subscription management:** Handle `request.recurring` for automatic billing renewals
-- **Compliance workflows:** Update user permissions when `compliance.updated` shows KYC approval
-- **Real-time dashboards:** Display live payment status using `payment.processing` subStatus values
+- **Failure handling:** Detect unsuccessful payment attempts with `payment.failed`
- **Payer-funnel visibility:** Track wallet connection and signature progress on the Secure Payment Page via `secure_payment.user_event`
- **Payer-wallet monitoring:** Detect attempts by wallets that are not allowed to pay via `secure_payment.access_rejected`
## Implementation
-
- Complete technical documentation with setup, payloads, and code examples
+
+ Endpoint setup, recipient routing, and payloads.
-
- Working webhook handlers with Express.js and Next.js
+
+ Working webhook handlers with Express.js
POST /v1/webhook to create, GET/PUT/DELETE to manage, /test to fire test deliveries
+
+
+ Register and test an endpoint with an `x-orchestrator-key`.
+
diff --git a/api-reference/webhooks.mdx b/api-reference/webhooks.mdx
index 3648cc5..0c0f2d8 100644
--- a/api-reference/webhooks.mdx
+++ b/api-reference/webhooks.mdx
@@ -1,26 +1,48 @@
---
title: "Webhooks"
-description: "Complete webhook implementation guide with event types, security, and retry configuration"
+description: "Register signed webhook endpoints, identify who receives each event, and process payloads safely."
---
## Overview
-Webhooks deliver real-time notifications when payment and request events occur. Configure your endpoints to receive HMAC-signed POST requests with automatic retry logic and comprehensive event data.
+Webhooks notify your server when Request Network processes a payment, completes KYT screening, or finishes hosted onboarding. Every delivery is an HMAC-signed `POST` request.
-## Webhook Configuration
+A platform owns its Client IDs and Secure Payments. When it links a Client ID to an orchestrator, the orchestrator can create Secure Payments on the platform's behalf. Some events are then delivered to both the platform's Client ID endpoint and the linked orchestrator's endpoint.
-Manage webhooks in the [Dashboard](https://dashboard.request.network) or programmatically through the Auth API at `auth.request.network`. Each webhook is scoped to the Client ID that creates it; events for any payment link or request created with that Client ID are delivered to that webhook.
+You do not subscribe an endpoint to individual event types. An active endpoint receives the events available to the Client ID or orchestrator that registered it.
-### Create a webhook
+
+New platform endpoints are scoped to a Client ID. Existing platform-wide endpoints continue to receive their platform events, but you cannot create new platform-wide endpoints through the current registration flow.
+
+
+## Choose who receives notifications
+
+Register an endpoint for the role that needs the event:
+
+| Recipient | Authentication | Receives |
+| --- | --- | --- |
+| Platform's Client ID | `x-client-id` or the platform's Dashboard session | Events for Secure Payments created with that Client ID. |
+| Orchestrator | `x-orchestrator-key` | Hosted-onboarding events and events for Secure Payments it created on behalf of a linked platform. |
+
+Register each role separately, even if both roles use the same callback URL. Each registration has its own signing secret.
+
+
+**Orchestrator integration:** Registering and managing an orchestrator endpoint requires only `x-orchestrator-key`. Use paired `x-client-id` and `x-orchestrator-key` authentication only when you [create a Secure Payment for a linked platform](/orchestrators/client-id-linking#create-payment-links-for-a-platform).
+
+
+## Register a platform Client ID webhook
+
+Use the platform's Client ID to register an endpoint:
```bash
curl -X POST "https://auth.request.network/v1/webhook" \
-H "Content-Type: application/json" \
- -H "x-client-id: YOUR_CLIENT_ID" \
- -d '{ "url": "https://yourapp.com/webhooks/request-network" }'
+ -H "x-client-id: cli_YOUR_CLIENT_ID" \
+ -d '{ "url": "https://platform.example.com/webhooks/request-network" }'
```
**Response (201 Created):**
+
```json
{
"id": "01KJC2WX8EH4MP3DHZB2YQ7N9G",
@@ -29,196 +51,108 @@ curl -X POST "https://auth.request.network/v1/webhook" \
```
-The `secret` is only returned once at creation. Store it securely — you cannot retrieve it again. Use HTTPS in production. `localhost` URLs are accepted for local testing.
+Save `secret` when you create the endpoint. Request Network returns it only once. Use HTTPS in production; `localhost` is accepted for local development.
-### Manage webhooks
+If you use a Dashboard session instead of `x-client-id`, include the Client ID in the request body when creating or testing an endpoint. See the [Auth API webhook endpoints](https://auth.request.network/open-api/#tag/webhook) for that session-authenticated form.
-All endpoints accept `x-client-id` and operate on the webhooks owned by that Client ID.
+### Manage and test platform endpoints
| Method | Path | Purpose |
| --- | --- | --- |
-| `GET` | `/v1/webhook` | List webhooks for this Client ID |
-| `PUT` | `/v1/webhook/:webhookId` | Toggle active / inactive |
-| `DELETE` | `/v1/webhook/:webhookId` | Permanently delete |
-| `POST` | `/v1/webhook/test` | Body `{ "eventType": "payment.confirmed" }` — fire a test delivery |
+| `GET` | `/v1/webhook` | List the Client ID's endpoints. |
+| `PUT` | `/v1/webhook/:webhookId` | Activate or deactivate an endpoint. |
+| `DELETE` | `/v1/webhook/:webhookId` | Permanently remove an endpoint. |
+| `POST` | `/v1/webhook/test` | Send a signed test event to active endpoints. |
-Open the [Auth API Scalar docs](https://auth.request.network/open-api/#tag/webhook) to call these interactively with your wallet session — signing in to the [Dashboard](https://dashboard.request.network) sets the session cookie that's shared across all `*.request.network` services.
+Test a platform endpoint with the same Client ID:
-### Local Development
-Use [ngrok](https://ngrok.com/docs/traffic-policy/getting-started/agent-endpoints/cli) to receive webhooks locally, then pass the public URL to `POST /v1/webhook`:
```bash
-ngrok http 3000
-# Use the HTTPS URL (e.g., https://abc123.ngrok.io/webhook) as the webhook URL
+curl -X POST "https://auth.request.network/v1/webhook/test" \
+ -H "Content-Type: application/json" \
+ -H "x-client-id: cli_YOUR_CLIENT_ID" \
+ -d '{ "eventType": "payment.confirmed" }'
```
-## Event Types
+Test deliveries include `x-request-network-test: true`. They use the same signature format and an example payload; they do not represent a real payment or screening decision.
-
-See [Payload Examples](#payload-examples) below for detailed webhook structures.
-
+## Register an orchestrator webhook
-### Payment Events (core)
+Use the orchestrator key to register an endpoint:
-| Event | Description | Context | Primary Use |
-|-------|-------------|---------|-------------|
-| `payment.confirmed` | Payment fully completed and settled | After blockchain confirmation | Complete fulfillment, release goods |
-| `payment.partial` | Partial payment received for request | Installments, partial orders | Update balance, allow additional payments |
-| `payment.failed` | Payment execution failed | Recurring payments, cross-chain transfers | Notify failure, retry logic, pause subscriptions |
-| `payment.refunded` | Payment has been refunded to payer | Cross-chain payment failures, refund scenarios | Update order status, notify customer |
+```bash
+curl -X POST "https://api.request.network/v2/orchestrators/webhooks" \
+ -H "x-orchestrator-key: orc_YOUR_ORCHESTRATOR_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{ "url": "https://orchestrator.example.com/webhooks/request-network" }'
+```
-### Processing Events
+**Response (201 Created):**
-| Event | Description | Context | Primary Use |
-|-------|-------------|---------|-------------|
-| `payment.processing` | Crypto-to-fiat payment in progress | **subStatus values:** initiated, pending_internal_assessment, ongoing_checks, sending_fiat, fiat_sent, bounced | Track crypto-to-fiat payment status, update UI |
+```json
+{
+ "webhook": {
+ "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
+ "url": "https://orchestrator.example.com/webhooks/request-network",
+ "isActive": true,
+ "createdAt": "2026-08-14T10:00:00.000Z"
+ },
+ "secret": "4f2c5a8d1b3e6f709c2d4a7b0e1f3c5d8a2b4e6f9c1d3a5b7e0f2c4d6a8b1e3f"
+}
+```
-### Request Events
+
+Save `secret` when you register the endpoint. Request Network never includes it in list, deactivate, or reactivate responses.
+
-| Event | Description | Context | Primary Use |
-|-------|-------------|---------|-------------|
-| `request.recurring` | New recurring request generated | Subscription renewals, scheduled payments | Send renewal notifications, update billing |
+### Manage and test orchestrator endpoints
-### Compliance Events
+| Method | Path | Purpose |
+| --- | --- | --- |
+| `GET` | `/v2/orchestrators/webhooks` | List the orchestrator's endpoints, including inactive endpoints. |
+| `DELETE` | `/v2/orchestrators/webhooks/:id` | Deactivate an endpoint without removing its URL or signing secret. |
+| `PATCH` | `/v2/orchestrators/webhooks/:id` | Reactivate an endpoint. |
+| `POST` | `/v2/orchestrators/webhooks/test` | Send a signed test event to every active endpoint. |
-| Event | Description | Context | Primary Use |
-|-------|-------------|---------|-------------|
-| `compliance.updated` | KYC or agreement status changed | **kycStatus values:** not_started, pending, approved, rejected, retry_required
**agreementStatus values:** not_started, pending, completed, rejected, failed | Update user permissions, notify status |
-| `payment_detail.updated` | Bank account verification status updated | States: approved, failed, pending | Enable fiat payments, update profiles |
+To test an orchestrator endpoint:
-### Secure Payment Events
+```bash
+curl -X POST "https://api.request.network/v2/orchestrators/webhooks/test" \
+ -H "x-orchestrator-key: orc_YOUR_ORCHESTRATOR_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{ "eventType": "client_id.linked" }'
+```
-| Event | Description | Context | Primary Use |
-|-------|-------------|---------|-------------|
-| `secure_payment.user_event` | Payer progressed through a step of the Secure Payment Page | **userEvent values:** wallet_connected, payment_sent_to_wallet, payment_approved_in_wallet | Real-time payer-funnel visibility, drop-off analytics |
-| `secure_payment.access_rejected` | A wallet not on a payment's payer-wallet allowlist attempted to access or pay it | Incoming Secure Payments with `allowedPayerAddresses` | Monitor rejected payer attempts |
+Deactivation preserves the endpoint. Registering the same URL again is rejected, including while it is inactive; reactivate it instead. To change the URL, deactivate the old endpoint and register the new one.
-Sent to the same registered webhook endpoints as every other event — same Client ID scoping, `x-request-network-signature` HMAC verification, delivery headers, timeout, and 1s/5s/15s retry semantics described elsewhere on this page.
+## Current webhook events
-The `userEvent` field distinguishes the 3 funnel steps:
+These are the events for current Secure Payment and orchestrator integrations. The platform's Client ID also receives `payment.confirmed`, KYT, and payer-activity events for Secure Payments created by its linked orchestrator.
-| `userEvent` | Meaning |
-|-------------|---------|
-| `wallet_connected` | The payer successfully connected a wallet on the secure payment page |
-| `payment_sent_to_wallet` | The payment transaction was handed to the payer's wallet for signature |
-| `payment_approved_in_wallet` | The payer approved/signed the payment in their wallet. `properties` includes the submission id (e.g. tx hash / user-operation hash) |
+| Event | Platform's Client ID | Linked orchestrator | Notes |
+| --- | --- | --- | --- |
+| `client_id.linked` | No | Yes | Sent after hosted onboarding completes. Direct API links do not emit this event. |
+| `payment.confirmed` | Yes | Yes | Both receive it when the linked orchestrator created the Secure Payment for the platform. |
+| `payment.failed` | Yes | No | Payment execution fails. |
+| `kyt.screening.completed` | Yes | Yes | Sent after a definitive `approved` or `rejected` result. Provider errors do not emit it. |
+| `secure_payment.user_event` | Yes | Yes | Best-effort payer activity from the Secure Payment Page. Do not use it as a settlement signal. |
+| `secure_payment.access_rejected` | Yes | No | A wallet outside an incoming payment's payer-wallet allowlist tries to access or pay it. |
-`securePaymentToken` is the platform's correlation key, returned when the secure payment was created. `requestId` is present only when exactly one request is linked to the secure payment (see `requestIds` for the full list). `timestamp` is server-stamped on receipt. `occurredAt` and `properties` are **client-reported telemetry from the payer's browser** — useful for analytics, but not authoritative.
-
-`secure_payment.user_event` is best-effort browser telemetry. Navigation, network errors, or browser extensions can prevent the API from receiving it. Webhook retries begin only after the API accepts the event. Do not treat an absent event as evidence that the payer did not take the step; use `payment.confirmed` for settlement and reconciliation.
-
-When the Secure Payment Page includes wallet information in `properties`, it uses `wallet_address_hashed` rather than a raw wallet address.
+`payment.confirmed` is the settlement signal. `secure_payment.user_event` is browser-reported activity: navigation, network failures, or browser extensions can prevent Request Network from receiving it. Its absence does not prove that the payer did not take that step.
-### Payer-wallet access rejections
-
-`secure_payment.access_rejected` is generated server-side when a wallet that is not on an incoming payment's `allowedPayerAddresses` allowlist tries to access or pay it. It is not emitted for KYT decisions. See [Restrict payer wallets](/use-cases/restrict-payer-wallets) to configure the allowlist.
-
-The event is sent to the payment's platform-wide and Client ID webhooks, not to an orchestrator webhook. Repeated attempts by the same wallet on the same payment are normally suppressed for 10 minutes. If every configured webhook endpoint fails, the next access attempt can trigger another notification.
-
-## Security Implementation
-
-### Signature Verification
-Every webhook includes an HMAC SHA-256 signature in the `x-request-network-signature` header:
-
-```javascript
-import crypto from "node:crypto";
-
-function verifyWebhookSignature(rawBody, signature, secret) {
- const expectedSignature = crypto
- .createHmac("sha256", secret)
- .update(rawBody)
- .digest("hex");
-
- try {
- return crypto.timingSafeEqual(
- Buffer.from(signature),
- Buffer.from(expectedSignature)
- );
- } catch {
- return false;
- }
-}
-
-// Usage in your webhook handler
-app.post("/webhook", (req, res) => {
- const signature = req.headers["x-request-network-signature"];
-
- if (!verifyWebhookSignature(req.rawBody, signature, WEBHOOK_SECRET)) {
- return res.status(401).json({ error: "Invalid signature" });
- }
-
- // Parse JSON after verification
- const body = JSON.parse(req.rawBody.toString("utf8"));
-
- // Process webhook...
- res.status(200).json({ success: true });
-});
-```
-
-### Security Requirements
-- **HTTPS only:** Production webhooks require HTTPS endpoints
-- **Always verify signatures:** Never process unverified webhook requests
-- **Keep secrets secure:** Store signing secrets as environment variables
-- **Return 2xx for success:** Any 2xx status code confirms successful processing
-
-## Request Headers
-
-Each webhook request includes these headers:
-
-| Header | Description | Example |
-|--------|-------------|---------|
-| `x-request-network-signature` | HMAC SHA-256 signature | `a1b2c3d4e5f6...` |
-| `x-request-network-delivery` | Unique delivery ID (ULID) | `01ARZ3NDEKTSV4RRFFQ69G5FAV` |
-| `x-request-network-retry-count` | Current retry attempt (0-3) | `0` |
-| `x-request-network-test` | Present for test webhooks | `true` |
-| `content-type` | Always JSON | `application/json` |
-
-## Retry Logic
-
-### Automatic Retries
-- **Max attempts:** 3 retries (4 total attempts)
-- **Retry delays:** 1s, 5s, 15s
-- **Trigger conditions:** Non-2xx response codes, timeouts, connection errors
-- **Timeout:** 5 seconds per request
-
-### Response Handling
-```javascript
-// ✅ Success - no retry
-res.status(200).json({ success: true });
-res.status(201).json({ created: true });
-
-// ❌ Error - triggers retry
-res.status(401).json({ error: "Unauthorized" });
-res.status(404).json({ error: "Resource not found" });
-res.status(500).json({ error: "Internal server error" });
-```
-
-### Error Logging
-Request API logs all webhook delivery failures with:
-- Endpoint URL
-- Attempt number
-- Error details
-- Final failure after all retries
-
-## Payload Examples
+## Payload identity and correlation
-All payment events include an `explorer` field linking to [Request Scan](https://scan.request.network) for transaction details.
+For a Secure Payment created with a Client ID, payloads identify the platform's Client ID with `clientId`. When the payment was created through an orchestrator, they also include `orchestratorId`.
-**Common Fields:**
-- `requestId` / `requestID`: Unique identifier for the payment request
-- `paymentReference`: Short reference, also unique to a request, used to link payments to the request
-- `timestamp`: ISO 8601 formatted event timestamp
-- `paymentProcessor`: Either `request-network` (crypto) or `request-tech` (fiat)
-- `payerAddress`: Resolved payer wallet — the on-chain sender for plain direct payments, or the resolved payer for recurring and intent-based flows (Secure Payment Page, LiFi, Safe, ERC-4337, multicall). `null` when it cannot be determined. Included on `payment.confirmed` and `payment.partial` events.
-- `payerEoaAddress`: The payer's connected wallet address. It can differ from `payerAddress` when a smart account is used. `null` when unavailable. Included on `payment.confirmed` and `payment.partial` events.
+`orchestratorId` is the orchestrator recorded when the Secure Payment was created. A later link, unlink, or relink does not change past payment events. Use `requestId`, `paymentToken`, or `securePaymentToken` to correlate the event with your own records.
-The `client_id.linked` and `kyt.screening.completed` examples below are delivered to an orchestrator endpoint. Register that endpoint with `x-orchestrator-key` as described in [Orchestrator webhooks](/orchestrators/webhooks).
+Payment events include an `explorer` URL for [Request Scan](https://scan.request.network) when one is available. `requestId` and `requestID` identify the request, and `paymentReference` is its short unique reference.
-### Client ID linked
+### Hosted onboarding completed
-`client_id.linked` is sent to an orchestrator after a platform completes hosted onboarding from a link intent.
+`client_id.linked` lets the orchestrator match hosted onboarding to the link intent it created. Use `linkId` or `intentId` to deduplicate repeated deliveries.
```json
{
@@ -236,9 +170,10 @@ The `client_id.linked` and `kyt.screening.completed` examples below are delivere
}
```
-Use `intentId` or `externalId` to match this event to your onboarding record. Use `linkId` or `intentId` to identify a repeated delivery.
+### Payment confirmed
+
+Do not fulfill an order until you receive `payment.confirmed`.
-### Payment Confirmed
```json
{
"event": "payment.confirmed",
@@ -249,70 +184,23 @@ Use `intentId` or `externalId` to match this event to your onboarding record. Us
"amount": "100.0",
"totalAmountPaid": "100.0",
"expectedAmount": "100.0",
- "timestamp": "2025-10-03T14:30:00Z",
"txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"payerAddress": "0x92Fc3406Fc6BB7A76aC63b2E8b9d02b1B9C3e4d5",
"payerEoaAddress": "0x7A1F20C4D58E9B0A3C6D4E2F1B8A5C7D9E0F1234",
"network": "ethereum",
"currency": "USDC",
"paymentCurrency": "USDC",
- "isCryptoToFiat": false,
- "subStatus": "",
- "paymentProcessor": "request-network",
- "fees": [
- {
- "type": "network",
- "amount": "0.02",
- "currency": "ETH"
- }
- ]
+ "fees": [],
+ "clientId": "cli_PLATFORM_CLIENT_ID",
+ "orchestratorId": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
+ "timestamp": "2026-08-14T10:00:00.000Z"
}
```
-### Payment Processing
-```json
-{
- "event": "payment.processing",
- "requestId": "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93",
- "requestID": "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93",
- "paymentReference": "0x2c3366941274c34c",
- "offrampId": "offramp_test123456789",
- "timestamp": "2025-10-03T14:35:00Z",
- "subStatus": "ongoing_checks",
- "paymentProcessor": "request-tech",
- "rawPayload": {
- "status": "ongoing_checks",
- "providerId": "provider_test123"
- }
-}
-```
+`payerAddress` is the address used to make the payment. `payerEoaAddress` is the payer's connected wallet and can differ when a smart account is used. Either can be `null` when unavailable.
-### Payment Partial
-```json
-{
- "event": "payment.partial",
- "requestId": "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93",
- "requestID": "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93",
- "paymentReference": "0x2c3366941274c34c",
- "explorer": "https://scan.request.network/request/0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93",
- "amount": "50.0",
- "totalAmountPaid": "50.0",
- "expectedAmount": "100.0",
- "timestamp": "2025-10-03T14:30:00Z",
- "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
- "payerAddress": "0x92Fc3406Fc6BB7A76aC63b2E8b9d02b1B9C3e4d5",
- "payerEoaAddress": "0x7A1F20C4D58E9B0A3C6D4E2F1B8A5C7D9E0F1234",
- "network": "ethereum",
- "currency": "USDC",
- "paymentCurrency": "USDC",
- "isCryptoToFiat": false,
- "subStatus": "",
- "paymentProcessor": "request-network",
- "fees": []
-}
-```
+### Payment failed
-### Payment Failed
```json
{
"event": "payment.failed",
@@ -324,25 +212,9 @@ Use `intentId` or `externalId` to match this event to your onboarding record. Us
}
```
-### Compliance Updated
-```json
-{
- "event": "compliance.updated",
- "clientUserId": "user_test123456789",
- "kycStatus": "approved",
- "agreementStatus": "completed",
- "isCompliant": true,
- "timestamp": "2025-10-03T14:30:00Z",
- "rawPayload": {
- "verificationLevel": "full",
- "documents": "verified"
- }
-}
-```
-
-### KYT Screening Completed
+### KYT screening completed
-`kyt.screening.completed` is sent after an orchestrator-linked payment reaches an `approved` or `rejected` screening result.
+KYT results are sent only for definitive `approved` and `rejected` outcomes. If an orchestrator-controlled plan has a backup provider, Request Network tries it after a technical provider failure before deciding whether screening completed.
```json
{
@@ -353,14 +225,23 @@ Use `intentId` or `externalId` to match this event to your onboarding record. Us
"smartAccountAddress": null,
"status": "approved",
"provider": "hypernative",
- "policyId": "11111111-1111-4111-8111-811111111111",
+ "policyId": "11111111-1111-4111-8111-111111111111",
+ "clientId": "cli_PLATFORM_CLIENT_ID",
+ "orchestratorId": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
"timestamp": "2026-08-14T10:00:00.000Z"
}
```
-`provider` is the provider that produced the result. `policyId` is `null` when the provider account default was used.
+### Secure Payment user event
+
+`secure_payment.user_event` reports `wallet_connected`, `payment_sent_to_wallet`, or `payment_approved_in_wallet`. `occurredAt` and `properties` come from the payer's browser; `timestamp` is added by Request Network when it receives the event.
+
+| `userEvent` | Meaning |
+| --- | --- |
+| `wallet_connected` | The payer connected a wallet on the Secure Payment Page. |
+| `payment_sent_to_wallet` | The payment transaction was handed to the payer's wallet for signature. |
+| `payment_approved_in_wallet` | The payer approved the payment in its wallet. This is not confirmation that it settled on-chain. |
-### Secure Payment User Event
```json
{
"event": "secure_payment.user_event",
@@ -368,7 +249,8 @@ Use `intentId` or `externalId` to match this event to your onboarding record. Us
"securePaymentToken": "spt_3fk29ax7...",
"requestId": "01JD3E6JD46KY4KKV7X9V0MZ7W",
"requestIds": ["01JD3E6JD46KY4KKV7X9V0MZ7W"],
- "orchestratorId": "orch_12345",
+ "clientId": "cli_PLATFORM_CLIENT_ID",
+ "orchestratorId": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
"occurredAt": "2026-08-05T14:03:21.512Z",
"timestamp": "2026-08-05T14:03:22.104Z",
"properties": {
@@ -381,229 +263,106 @@ Use `intentId` or `externalId` to match this event to your onboarding record. Us
}
```
-### Secure Payment Access Rejected
+### Payer-wallet access rejected
+
+`secure_payment.access_rejected` is emitted when a wallet that is not on an incoming payment's `allowedPayerAddresses` allowlist tries to access or pay it. It is not a KYT result. Repeated attempts by the same wallet on the same payment are normally suppressed for 10 minutes.
+
```json
{
"event": "secure_payment.access_rejected",
"requestId": "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93",
"attemptedPayerWalletAddress": "0x2e2e5c79f571ef1658d4c2d3684a1fe97dd30570",
+ "clientId": "cli_PLATFORM_CLIENT_ID",
+ "orchestratorId": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
"timestamp": "2026-08-10T10:05:00.000Z"
}
```
+See [Restrict payer wallets](/use-cases/restrict-payer-wallets) to configure the allowlist.
+
| Field | Description |
-|-------|-------------|
+| --- | --- |
| `requestId` | The request the wallet tried to access. |
| `attemptedPayerWalletAddress` | The rejected wallet address. EVM addresses are lowercased; TRON addresses keep their original case. |
| `timestamp` | When Request Network emitted the event. |
-Use `POST /v1/webhook/test` with `{ "eventType": "secure_payment.access_rejected" }` to test this event without a rejected access attempt.
-
-## Implementation Examples
-
-For a complete working example, see [Webhook reconciliation](/use-cases/webhook-reconciliation) which implements webhook handling for payment notifications.
-
-
-
-```javascript
-import express from "express";
-import crypto from "node:crypto";
-
-const app = express();
-const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
-
-// Use raw body parser to capture exact request bytes for signature verification
-app.use(
- express.raw({
- type: "application/json",
- verify: (req, _res, buf) => {
- req.rawBody = buf;
- },
- })
-);
-
-app.post("/webhook/payment", async (req, res) => {
- try {
- // Verify signature against raw body
- const signature = req.headers["x-request-network-signature"];
- const deliveryId = req.headers["x-request-network-delivery"];
- const rawBody = req.rawBody;
-
- const expectedSignature = crypto
- .createHmac("sha256", WEBHOOK_SECRET)
- .update(rawBody)
- .digest("hex");
-
- if (!signature || !deliveryId) {
- return res.status(400).json({ error: "Missing webhook headers" });
- }
-
- if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
- return res.status(401).json({ error: "Invalid signature" });
- }
-
- // Parse JSON only after verifying signature
- const body = JSON.parse(rawBody.toString("utf8"));
- const isTest = req.headers["x-request-network-test"] === "true";
-
- if (isTest) {
- console.log("Received test webhook");
- }
-
- // Process webhook based on event type
- const { event, requestId } = body;
-
- switch (event) {
- case "payment.confirmed":
- await handlePaymentConfirmed(body);
- break;
- case "payment.processing":
- await handlePaymentProcessing(body);
- break;
- case "compliance.updated":
- await handleComplianceUpdate(body);
- break;
- case "secure_payment.access_rejected":
- await recordPayerWalletRejection(
- requestId,
- body.attemptedPayerWalletAddress,
- deliveryId,
- );
- break;
- default:
- console.log(`Unhandled event: ${event}`);
- }
-
- return res.status(200).json({ success: true });
-
- } catch (error) {
- console.error("Webhook processing error:", error);
- return res.status(500).json({ error: "Processing failed" });
- }
-});
-```
-
-
-
-```javascript
-// app/api/webhook/route.ts
-import crypto from "node:crypto";
-import { NextResponse } from "next/server";
-
-export async function POST(request: Request) {
- try {
- // Read raw body for signature verification
- const rawBody = await request.text();
- const signature = request.headers.get("x-request-network-signature");
- const expectedSignature = crypto
- .createHmac("sha256", process.env.WEBHOOK_SECRET!)
- .update(rawBody)
- .digest("hex");
-
- if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) {
- return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
- }
-
- // Parse JSON after verifying signature
- const body = JSON.parse(rawBody);
-
- // Process webhook
- const { event, requestId } = body;
-
- // Your business logic here
- await processWebhookEvent(event, body);
-
- return NextResponse.json({ success: true }, { status: 200 });
-
- } catch (error) {
- console.error("Webhook error:", error);
- return NextResponse.json(
- { error: "Internal server error" },
- { status: 500 }
- );
- }
-}
-```
-
-
+## Delivery requirements
-## Testing
+Verify `x-request-network-signature` against the raw request body before you parse it. Use `x-request-network-delivery` as your durable idempotency key: Request Network retries failed deliveries, so the same event can arrive more than once.
-### Test deliveries
+- Use HTTPS for production endpoints.
+- Store each endpoint's signing secret securely; it is returned only when that endpoint is created.
+- Handle unknown event names safely so a new event does not fail your endpoint.
-Fire a test webhook from the Auth API:
+For an Express handler, local testing with ngrok, and reconciliation patterns, see [Webhook reconciliation](/use-cases/webhook-reconciliation).
-```bash
-curl -X POST "https://auth.request.network/v1/webhook/test" \
- -H "Content-Type: application/json" \
- -H "x-client-id: YOUR_CLIENT_ID" \
- -d '{ "eventType": "payment.confirmed" }'
-```
+### Delivery headers
-Or call it interactively from the [Auth API Scalar docs](https://auth.request.network/open-api/#tag/webhook/POST/v1/webhook/test).
+| Header | Description |
+| --- | --- |
+| `x-request-network-signature` | HMAC-SHA256 of the raw JSON body, encoded as hex. |
+| `x-request-network-delivery` | Stable delivery ID. Use it as the idempotency key. |
+| `x-request-network-retry-count` | Current attempt: `0` to `3`. |
+| `x-request-network-test` | `true` for test deliveries only. |
+| `content-type` | `application/json`. |
-Test deliveries arrive at all active webhooks for that Client ID and include the `x-request-network-test: true` header so handlers can branch on test vs real.
+### Retry policy
-### Test Webhook Identification
-Test webhooks include the `x-request-network-test: true` header:
+Request Network retries non-2xx responses, timeouts, and connection errors. A delivery has four attempts in total: the initial request plus retries after 1, 5, and 15 seconds. Each attempt has a five-second timeout.
-```javascript
-app.post("/webhook", (req, res) => {
- const isTest = req.headers["x-request-network-test"] === "true";
-
- if (isTest) {
- console.log("Received test webhook");
- // Handle test scenario
- }
-
- // Process normally...
-});
-```
+Return a 2xx response only after you have safely recorded the delivery. If processing fails, return a non-2xx response so Request Network retries it.
-## Best Practices
+## Troubleshooting
-### Error Handling
-- **Implement idempotency:** Use delivery IDs to prevent duplicate processing
-- **Graceful degradation:** Handle unknown event types without errors
+**Signature verification fails**
-### Performance
-- **Timeout management:** Complete processing within 5 seconds
+- Verify the signature against the raw request body, not a parsed or re-serialized body.
+- Confirm that you saved the secret returned when this specific endpoint was created.
+- Confirm that the endpoint's platform or orchestrator registration is active.
-## Troubleshooting
+**No webhook arrives**
-### Common Issues
+- Confirm that the relevant Client ID or orchestrator has an active endpoint.
+- Confirm that the event belongs to that recipient in [Current webhook events](#current-webhook-events).
+- Send a test event to verify endpoint reachability and signature handling.
-**Signature verification fails:**
-- Check your signing secret matches the value returned by `POST /v1/webhook` at creation
-- Ensure you're using the raw request body for signature calculation
-- Verify HMAC SHA-256 implementation
+## Legacy integrations
-**Webhooks not received:**
-- Confirm endpoint URL is accessible via HTTPS
-- Verify endpoint returns 2xx status codes
-- Confirm the webhook is `active` via `GET /v1/webhook` (toggle with `PUT /v1/webhook/:id`)
+These events remain available for existing API integrations. They do not apply to current Dashboard, Secure Payment Page, or orchestrator workflows.
-### Debugging Tips
-- Use ngrok request inspector to see raw webhook data
-- Monitor retry counts in headers to identify issues
-- Fire test deliveries via `POST /v1/webhook/test`
+| Event | Legacy flow |
+| --- | --- |
+| `payment.partial` | Partial-payment flow |
+| `payment.refunded` | Existing refund flows |
+| `payment.processing` | Crypto-to-fiat processing |
+| `compliance.updated` | Crypto-to-fiat compliance updates |
+| `payment_detail.updated` | Crypto-to-fiat bank-detail updates |
+| `request.recurring` | Recurring requests |
-## Related Documentation
+## Related webhook pages
-
- High-level webhook concepts and workflow
+
+ Learn the event categories and common webhook use cases.
-
- Complete webhook implementation example
+
+ Implement a signed webhook handler and process events safely.
+
+
+
+## Related
+
+
+
+ Link a platform's Client ID and receive hosted-onboarding results.
-
- API credential setup and webhook security
+
+ Configure transaction screening for linked platforms.
-
- Manage Client IDs, payment destinations, and webhooks
+
+ Allow only selected payer wallets for an incoming payment.
diff --git a/docs.json b/docs.json
index 9dd938f..2c67322 100644
--- a/docs.json
+++ b/docs.json
@@ -429,6 +429,10 @@
{
"source": "/webhooks",
"destination": "/api-features/webhooks-events"
+ },
+ {
+ "source": "/orchestrators/webhooks",
+ "destination": "/api-reference/webhooks"
}
],
"navigation": {
@@ -549,7 +553,6 @@
"orchestrators/fees",
"orchestrators/client-id-linking",
"orchestrators/kyt-plans",
- "orchestrators/webhooks",
"orchestrators/whitelabel-branding"
]
}
diff --git a/orchestrators/client-id-linking.mdx b/orchestrators/client-id-linking.mdx
index 4946285..3bedc89 100644
--- a/orchestrators/client-id-linking.mdx
+++ b/orchestrators/client-id-linking.mdx
@@ -9,7 +9,7 @@ Link a platform's [Client ID](/api-features/client-id-management) (`cli_*`) to y
All linking endpoints use the `x-orchestrator-key` header.
-If you use hosted onboarding, [register an orchestrator webhook](/orchestrators/webhooks) before you send the onboarding URL. It receives the resulting client ID after the platform completes the flow.
+If you use hosted onboarding, [register an orchestrator webhook](/api-reference/webhooks#register-an-orchestrator-webhook) before you send the onboarding URL. It receives the resulting client ID after the platform completes the flow.
A client ID can be linked to one orchestrator only. A second link request is rejected, whether it is for the same orchestrator or a different one. If you need to check whether a link request succeeded, list your linked client IDs instead of retrying it. Unlinking removes the active connection, but does not make the client ID linkable again.
@@ -121,7 +121,7 @@ The hosted flow asks the platform to:
3. Review your screening plan, or choose a screening preference when you did not provide one.
4. Confirm the connection to your orchestrator.
-After confirmation, Request Network creates a new `cli_*` client ID, copies any allowed domains from the intent, links it to your orchestrator, and binds it to the platform's active payment destination. Active orchestrator webhooks receive a [`client_id.linked` event](/orchestrators/webhooks) with the client ID, link identifiers, and destination details. Direct links do not send this event.
+After confirmation, Request Network creates a new `cli_*` client ID, copies any allowed domains from the intent, links it to your orchestrator, and binds it to the platform's active payment destination. Active orchestrator webhooks receive a [`client_id.linked` event](/api-reference/webhooks#hosted-onboarding-completed) with the client ID, link identifiers, and destination details. Direct links do not send this event.
A link intent is single-use and expires after two days. Create a new intent if the platform has not completed the flow before it expires.
diff --git a/orchestrators/kyt-plans.mdx b/orchestrators/kyt-plans.mdx
index d98c91c..ed58045 100644
--- a/orchestrators/kyt-plans.mdx
+++ b/orchestrators/kyt-plans.mdx
@@ -72,7 +72,7 @@ Use paired authentication when you create a payment or payout for the linked pla
## Receive screening results
-Your active [orchestrator webhooks](/orchestrators/webhooks#receive-kyt-screeningcompleted) receive `kyt.screening.completed` after a linked payment reaches a definitive screening result. The event is sent for `approved` and `rejected` results, not provider errors.
+Your active [orchestrator webhooks](/api-reference/webhooks#kyt-screening-completed) receive `kyt.screening.completed` after a linked payment reaches a definitive screening result. The event is sent for `approved` and `rejected` results, not provider errors.
Test your receiver with the existing webhook test endpoint:
@@ -92,7 +92,7 @@ The test verifies delivery and signature handling with placeholder data. Create
Create the hosted onboarding link that carries an orchestrator KYT plan.
-
+
Verify and process `kyt.screening.completed` events.
diff --git a/orchestrators/overview.mdx b/orchestrators/overview.mdx
index d14de23..7a48390 100644
--- a/orchestrators/overview.mdx
+++ b/orchestrators/overview.mdx
@@ -9,7 +9,7 @@ An **orchestrator** is a fee and branding partner in Request Network. It is a fi
- **Link client IDs** — associate the developer [Client IDs](/api-features/client-id-management) (`cli_*` tokens) of the platforms you serve with your orchestrator.
- **Create payment links** — use your key with a linked client ID to create incoming payment links and outgoing payout links for a platform.
-- **Receive webhooks** — receive signed `client_id.linked` notifications after a platform completes hosted onboarding and `kyt.screening.completed` notifications after a definitive screening result.
+- **Receive webhooks** — receive signed hosted-onboarding, payment, KYT-screening, and payer-activity notifications for the flows you manage. See [Webhooks](/api-reference/webhooks#register-an-orchestrator-webhook).
- **Configure fees** — set [orchestrator fees](/orchestrators/fees) (and per-client-ID overrides) that apply to payments made under those client IDs.
- **Apply branding** — give the hosted [Secure Payment](/tools/secure-payments) experience your own [whitelabel branding](/orchestrators/whitelabel-branding).
@@ -64,7 +64,7 @@ All partner-facing orchestrator endpoints live under `/v2/orchestrators` and are
| `PATCH /v2/orchestrators/fee-configs/:id` | Update a fee config |
| `DELETE /v2/orchestrators/fee-configs/:id` | Disable a fee config |
-See [Client ID linking](/orchestrators/client-id-linking), [Whitelabel branding](/orchestrators/whitelabel-branding), [Orchestrator webhooks](/orchestrators/webhooks), and [Orchestrator fees](/orchestrators/fees) for details.
+See [Client ID linking](/orchestrators/client-id-linking), [Whitelabel branding](/orchestrators/whitelabel-branding), [Webhooks](/api-reference/webhooks#register-an-orchestrator-webhook), and [Orchestrator fees](/orchestrators/fees) for details.
## How orchestrators apply at payment time
@@ -91,8 +91,8 @@ curl -X POST "https://api.request.network/v2/secure-payments" \
Link client IDs directly or via onboarding link intents.
-
- Receive signed onboarding and KYT screening notifications.
+
+ Receive signed onboarding, payment, KYT-screening, and payer-activity notifications.
diff --git a/orchestrators/webhooks.mdx b/orchestrators/webhooks.mdx
deleted file mode 100644
index 29ce9a1..0000000
--- a/orchestrators/webhooks.mdx
+++ /dev/null
@@ -1,177 +0,0 @@
----
-title: "Orchestrator webhooks"
-description: "Receive signed notifications when a platform completes hosted onboarding or a transaction screening decision is reached."
----
-
-## Overview
-
-Orchestrator webhooks notify you about [hosted onboarding](/orchestrators/client-id-linking#onboard-a-platform-with-a-link-intent) and [transaction screening](/orchestrators/kyt-plans). They are owned by your orchestrator and use your `x-orchestrator-key`, not a platform's `x-client-id`.
-
-Register a webhook before you send an onboarding URL to a platform. Its active endpoints receive:
-
-- `client_id.linked` after the platform completes hosted onboarding.
-- `kyt.screening.completed` after an orchestrator-linked payment reaches a definitive screening result.
-
-## Register a webhook
-
-Register an endpoint with your orchestrator key:
-
-```bash
-curl -X POST "https://api.request.network/v2/orchestrators/webhooks" \
- -H "x-orchestrator-key: orc_YOUR_ORCHESTRATOR_KEY" \
- -H "Content-Type: application/json" \
- -d '{ "url": "https://partner.example.com/webhooks/request-network" }'
-```
-
-The response includes the webhook and a signing secret:
-
-```json
-{
- "webhook": {
- "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
- "url": "https://partner.example.com/webhooks/request-network",
- "isActive": true,
- "createdAt": "2026-08-14T10:00:00.000Z"
- },
- "secret": "4f2c5a8d1b3e6f709c2d4a7b0e1f3c5d8a2b4e6f9c1d3a5b7e0f2c4d6a8b1e3f"
-}
-```
-
-
-Save the signing secret when you register the webhook. Request Network returns it only once and never includes it in list, deactivate, or reactivate responses.
-
-
-Verify the `x-request-network-signature` HMAC-SHA256 header against the raw request body before you process an event. See the [webhook reconciliation guide](/use-cases/webhook-reconciliation) for a signature-verifying handler.
-
-Webhook deliveries may be retried. Make sure your endpoint can safely receive the same notification more than once.
-
-## Test your endpoint
-
-Send a signed mock event to every active endpoint. This example tests `client_id.linked`:
-
-```bash
-curl -X POST "https://api.request.network/v2/orchestrators/webhooks/test" \
- -H "x-orchestrator-key: orc_YOUR_ORCHESTRATOR_KEY" \
- -H "Content-Type: application/json" \
- -d '{ "eventType": "client_id.linked" }'
-```
-
-The response reports the delivery result:
-
-```json
-{
- "sent": 1,
- "failed": 0
-}
-```
-
-Test deliveries have the `x-request-network-test: true` header. They use the same signing process and payload shape as a real event, with placeholder values.
-
-## Receive `client_id.linked`
-
-`client_id.linked` is sent after a platform completes hosted onboarding from a link intent. It is not sent for a direct `POST /v2/orchestrators/client-ids` link.
-
-```json
-{
- "event": "client_id.linked",
- "clientId": "cli_PLATFORM_CLIENT_ID",
- "orchestratorId": "01ARZ3NDEKTSV4RRFFQ69G5FAW",
- "linkId": "01ARZ3NDEKTSV4RRFFQ69G5FAX",
- "intentId": "01ARZ3NDEKTSV4RRFFQ69G5FAY",
- "externalId": "merchant_123",
- "destinationId": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e@eip155:8453#B4FD67BB:0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
- "destinationWalletAddress": "0x742d35cc6634c0532925a3b844bc454e4438f44e",
- "chain": "base",
- "currency": "USDC",
- "timestamp": "2026-08-14T10:00:00.000Z"
-}
-```
-
-Use `intentId` or your optional `externalId` to match the event to your onboarding record, then save the returned `clientId`. Use the stable `linkId` or `intentId` to identify a repeated delivery.
-
-The `destinationId`, wallet address, chain, and currency identify the payment destination that the hosted flow bound to the new client ID. Use that client ID with paired authentication to create payment links for the platform.
-
-## Receive `kyt.screening.completed`
-
-`kyt.screening.completed` is sent to active orchestrator webhooks after an orchestrator-linked payment has a definitive `approved` or `rejected` screening result. It is not sent for a provider error. When an orchestrator-controlled plan has a backup provider, Request Network tries that backup after a technical failure before deciding whether screening completed.
-
-```json
-{
- "event": "kyt.screening.completed",
- "paymentToken": "01KYM5CZ51K0N1KJ4F8S73BE3N",
- "walletAddress": "0x2e2e5c79f571ef1658d4c2d3684a1fe97dd30570",
- "eoaAddress": "0x2e2e5c79f571ef1658d4c2d3684a1fe97dd30570",
- "smartAccountAddress": null,
- "status": "approved",
- "provider": "hypernative",
- "policyId": "11111111-1111-4111-8111-111111111111",
- "timestamp": "2026-08-14T10:00:00.000Z"
-}
-```
-
-| Field | Description |
-| --- | --- |
-| `paymentToken` | The secure-payment token whose screening result completed. |
-| `walletAddress` | The wallet evaluated for this payment. |
-| `eoaAddress` | The connected externally owned account, when available. |
-| `smartAccountAddress` | The connected smart-account address, when available; otherwise `null`. |
-| `status` | `approved` or `rejected`. |
-| `provider` | The provider that produced the definitive result. This can be the backup provider. |
-| `policyId` | The provider policy used, or `null` when the provider account default was used. |
-| `timestamp` | When Request Network evaluated the result. |
-
-To test this payload shape, send `{ "eventType": "kyt.screening.completed" }` to `POST /v2/orchestrators/webhooks/test`. Test deliveries use placeholder values and do not confirm which provider or policy a real payment would use.
-
-## Manage webhook endpoints
-
-List every endpoint registered to your orchestrator, including inactive ones:
-
-```bash
-curl "https://api.request.network/v2/orchestrators/webhooks" \
- -H "x-orchestrator-key: orc_YOUR_ORCHESTRATOR_KEY"
-```
-
-```json
-{
- "webhooks": [
- {
- "id": "01ARZ3NDEKTSV4RRFFQ69G5FAV",
- "url": "https://partner.example.com/webhooks/request-network",
- "isActive": true,
- "createdAt": "2026-08-14T10:00:00.000Z"
- }
- ]
-}
-```
-
-To stop delivery without removing the endpoint, deactivate it:
-
-```bash
-curl -X DELETE "https://api.request.network/v2/orchestrators/webhooks/01ARZ3NDEKTSV4RRFFQ69G5FAV" \
- -H "x-orchestrator-key: orc_YOUR_ORCHESTRATOR_KEY"
-```
-
-To resume delivery, reactivate the same endpoint:
-
-```bash
-curl -X PATCH "https://api.request.network/v2/orchestrators/webhooks/01ARZ3NDEKTSV4RRFFQ69G5FAV" \
- -H "x-orchestrator-key: orc_YOUR_ORCHESTRATOR_KEY"
-```
-
-Deactivation preserves the endpoint URL and signing secret. Registering the same URL again is rejected, even while it is inactive; reactivate it instead. To use a different URL, deactivate the old endpoint and register the new one.
-
-## Related
-
-
-
- Create a hosted onboarding link and receive its result through a webhook.
-
-
-
- Verify signatures and process webhook deliveries safely.
-
-
-
- Configure screening and receive its result.
-
-
diff --git a/use-cases/quickstart.mdx b/use-cases/quickstart.mdx
index 4c4b3e7..45738a9 100644
--- a/use-cases/quickstart.mdx
+++ b/use-cases/quickstart.mdx
@@ -186,134 +186,7 @@ Save the `clientId` value (e.g. `cli_nz1bj41szV2fvjm9pbxdIhro3ld4x4`) — you'll
## Step 4: Register a Webhook
-Webhooks let you receive real-time notifications when a payment is completed (or partially paid) for your payment links — no polling required.
-
-Webhooks are scoped to the Client ID that creates them. Any payment link created with that Client ID will trigger the webhook. The webhook payload includes the `clientId` field so you can identify which Client ID the payment was associated with.
-
-### Creating a webhook
-
-Open [auth.request.network/open-api](https://auth.request.network/open-api/#tag/webhook/POST/v1/webhook) and call:
-
-```http
-POST https://auth.request.network/v1/webhook
-x-client-id:
-Content-Type: application/json
-
-{
- "url": "https://mydomain.com/webhook"
-}
-```
-
-**Example response (201 Created):**
-
-```json
-{
- "id": "01KJC2WX8EH4MP3DHZB2YQ7N9G",
- "secret": "f3c189a4b5e6d7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2"
-}
-```
-
-
-The `secret` is only returned once at creation. Store it securely — you cannot retrieve it again. Use HTTPS in production. `localhost` URLs are accepted for local testing.
-
-
-### Managing webhooks
-
-Use the same Scalar docs page to list, toggle, or delete webhooks (all accept `x-client-id`):
-
-| Method | Path | Purpose |
-| --- | --- | --- |
-| `GET` | `/v1/webhook` | List webhooks for this Client ID |
-| `PUT` | `/v1/webhook/:webhookId` | Toggle active / inactive |
-| `DELETE` | `/v1/webhook/:webhookId` | Permanently delete |
-| `POST` | `/v1/webhook/test` | Body `{ "eventType": "payment.confirmed" }` — fire a test delivery |
-
-### Verifying webhook signatures
-
-Every webhook request includes a signature header so you can verify it came from Request Network:
-
-| Header | Description |
-| --- | --- |
-| `x-request-network-signature` | HMAC-SHA256 of the raw JSON body, signed with your webhook secret |
-| `x-request-network-delivery` | Unique delivery ID — use as an idempotency key |
-| `x-request-network-retry-count` | Retry attempt number (`0`–`3`) |
-| `x-request-network-test` | `true` only for test deliveries via `/v1/webhook/test` |
-
-To verify, compute `HMAC-SHA256(rawBody, webhookSecret)` and compare it to `x-request-network-signature` using a constant-time comparison.
-
-```typescript
-import { createHmac, timingSafeEqual } from "node:crypto";
-
-function verify(rawBody: string, signature: string | undefined, secret: string) {
- if (!signature) return false;
- const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
- const a = Buffer.from(expected, "hex");
- const b = Buffer.from(signature, "hex");
- return a.length === b.length && timingSafeEqual(a, b);
-}
-```
-
-Always verify against the **raw** request body before parsing.
-
-**Retries:** up to 3 retries (4 attempts total), default delays 1s / 5s / 15s, triggered on any non-2xx response, timeout, or connection error.
-
-### Webhook events for payment links
-
-When a payer completes a payment on a payment link you created, your webhook receives a `payment.confirmed` event (or `payment.partial` for partial payments).
-
-**Example `payment.confirmed` payload:**
-
-```json
-{
- "event": "payment.confirmed",
- "requestId": "01de2a889ee629c15b71b5d7964e3a7e87638c886be75bf1b9d2c1fbe64cf855fb",
- "paymentReference": "0xabc123...",
- "payee": "0x6923831ACf5c327260D7ac7C9DfF5b1c3cB3C7D7",
- "amount": "10.0",
- "totalAmountPaid": "10.0",
- "expectedAmount": "10.0",
- "timestamp": "2026-03-02T20:15:00.000Z",
- "txHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- "network": "sepolia",
- "currency": "FAU",
- "paymentCurrency": "FAU",
- "clientId": "cli_nz1bj41szV2fvjm9pbxdIhro3ld4x4",
- "origin": "https://mydomain.com"
-}
-```
-
-Key fields to look for:
-
-| Field | Description |
-| --- | --- |
-| `event` | `payment.confirmed` (fully paid) or `payment.partial` (partial payment) |
-| `requestId` | The request ID from when you created the payment link |
-| `clientId` | The Client ID used to create the payment link — use this to route events if you have multiple Client IDs |
-| `amount` | The amount paid in this transaction |
-| `totalAmountPaid` | Cumulative amount paid so far |
-| `expectedAmount` | The total amount expected |
-| `txHash` | On-chain transaction hash |
-| `network` | The blockchain network |
-| `currency` | The token used for payment |
-
-### All supported webhook events
-
-| Event | Description |
-| --- | --- |
-| `payment.confirmed` | Payment fully confirmed |
-| `payment.partial` | Partial payment received |
-| `payment.failed` | Payment failed |
-| `payment.refunded` | Payment refunded |
-| `payment.processing` | Offramp processing started |
-| `request.recurring` | A recurring request fired |
-| `payment_detail.updated` | Payment detail metadata changed |
-| `compliance.updated` | Compliance status changed |
-| `secure_payment.user_event` | Payer progressed through a Secure Payment Page step (`userEvent`: `wallet_connected`, `payment_sent_to_wallet`, `payment_approved_in_wallet`) — funnel telemetry, not a settlement signal |
-| `secure_payment.access_rejected` | A wallet not on a Secure Payment's payer-wallet allowlist tried to access or pay it |
-
-
-`secure_payment.user_event` is best-effort browser telemetry. The payer's browser can fail to reach the API, and retries begin only once the API has accepted the event — so a missing event is not evidence that the payer skipped the step. Use `payment.confirmed` for settlement and reconciliation.
-
+Register a webhook to receive `payment.confirmed` when the payment completes. For endpoint setup, event routing, payloads, and signature verification, see [Webhooks](/api-reference/webhooks).
## Step 5: Create a Secure Payment (Payment Link)
diff --git a/use-cases/restrict-payer-wallets.mdx b/use-cases/restrict-payer-wallets.mdx
index 8f70343..1ce84fb 100644
--- a/use-cases/restrict-payer-wallets.mdx
+++ b/use-cases/restrict-payer-wallets.mdx
@@ -48,7 +48,7 @@ EVM addresses are matched case-insensitively. TRON addresses are case-sensitive.
- Request Network stores the allowlist on the secure payment when you create it. To change who can pay, create a new payment.
- When an orchestrator creates a payment with paired authentication, it cannot retrieve the stored allowlist later with `x-orchestrator-key`. Keep the addresses you supplied alongside the payment record.
- A wallet that is not allowed receives the `wallet_not_allowed` access decision before it can continue to payment.
-- You can monitor rejected attempts with the [`secure_payment.access_rejected` webhook](/api-reference/webhooks#payer-wallet-access-rejections). It goes to the payment's platform-wide and Client ID webhooks, not to an orchestrator webhook.
+- You can monitor rejected attempts with the [`secure_payment.access_rejected` webhook](/api-reference/webhooks#payer-wallet-access-rejected). It goes to the payment's platform-wide and Client ID webhooks, not to an orchestrator webhook.
## Use an allowlist with KYT screening
diff --git a/use-cases/webhook-reconciliation.mdx b/use-cases/webhook-reconciliation.mdx
index 3cd3dd9..460a194 100644
--- a/use-cases/webhook-reconciliation.mdx
+++ b/use-cases/webhook-reconciliation.mdx
@@ -5,30 +5,30 @@ description: "Real-time payment notifications wired into your accounting, fulfil
## What you'll build
-A webhook handler that receives signed payment events from Request Network, verifies the signature, and triggers your downstream systems — order fulfillment, invoice closeout, accounting entries, customer email. Polling-free, idempotent, retry-safe.
+A webhook handler that receives signed events from Request Network, verifies the signature, and triggers your downstream systems — order fulfillment, invoice closeout, accounting entries, customer email. Polling-free, idempotent, retry-safe.
-**Audience:** any backend integrating Request Network where payment events drive state changes downstream.
+**Audience:** any backend integrating Request Network where payment or onboarding events drive state changes downstream.
-## The 10 events
+## Current event catalog
| Category | Event | When it fires |
| --- | --- | --- |
-| Payment (core) | `payment.confirmed` | Payment fully settled on-chain |
-| | `payment.partial` | Partial payment received, more expected |
-| | `payment.failed` | Payment execution failed (recurring, cross-chain) |
-| | `payment.refunded` | Payment refunded to payer |
-| Processing | `payment.processing` | Crypto-to-fiat offramp in progress (with detailed `subStatus`) |
-| Request | `request.recurring` | A new recurring billing cycle fired |
-| Compliance | `compliance.updated` | KYC or agreement status changed |
-| Bank details | `payment_detail.updated` | Bank account verification status changed |
+| Hosted onboarding | `client_id.linked` | Hosted onboarding linked a Client ID to an orchestrator |
+| Payment | `payment.confirmed` | Payment fully settled on-chain |
+| | `payment.failed` | Payment execution failed |
+| Transaction screening | `kyt.screening.completed` | Payer-wallet screening reached `approved` or `rejected` |
| Secure Payment Page | `secure_payment.user_event` | Payer progressed through a step of the Secure Payment Page (`userEvent`: `wallet_connected`, `payment_sent_to_wallet`, `payment_approved_in_wallet`) — funnel telemetry, **not** a settlement signal |
| Secure Payment | `secure_payment.access_rejected` | A wallet not on a payer-wallet allowlist tried to access or pay the payment |
-`secure_payment.user_event` is best-effort browser telemetry, and the stream is intentionally incomplete: the payer's browser can fail to reach the API, and retries begin only once the API has accepted the event. A missing event is not evidence that the payer skipped the step, so do not drive drop-off, notification, or reconciliation logic off its absence.
+`secure_payment.user_event` is best-effort browser activity. Its absence does not show that the payer skipped a step, so use `payment.confirmed` for settlement and reconciliation.
-For the full payload schemas, see the [Webhooks reference](/api-reference/webhooks).
+For delivery headers and currently documented payload examples, see the [Webhooks reference](/api-reference/webhooks). For events retained for older API integrations, see [Legacy integrations](/api-reference/webhooks#legacy-integrations).
-## Setup
+
+**Orchestrators:** Register and test your endpoint with `x-orchestrator-key` in the [orchestrator webhook setup](/api-reference/webhooks#register-an-orchestrator-webhook). The handler below works for both roles; use the signing secret returned for the endpoint you registered.
+
+
+## Set up a platform webhook
@@ -48,7 +48,7 @@ For the full payload schemas, see the [Webhooks reference](/api-reference/webhoo
## Handler — reference implementation
-A signature-verifying Express handler. It verifies against the **raw** body, uses constant-time comparison, passes the delivery ID to business handlers as their idempotency key, and lets Request Network retry a failed handler.
+A signature-verifying Express handler. It verifies against the **raw** body, uses constant-time comparison, passes the delivery ID to business handlers as their idempotency key, and lets Request Network retry a failed handler. The business functions in the example are placeholders: replace them with your own durable application logic.
```typescript
import { createHmac, timingSafeEqual } from "node:crypto";
@@ -83,11 +83,17 @@ app.post(
return res.status(401).send("invalid signature");
}
- // 2. Parse and route. Each business operation uses deliveryId as an
- // idempotency key in its own durable store.
- const event = JSON.parse(req.body.toString("utf8"));
-
try {
+ // 2. Parse and route. Each business operation uses deliveryId as an
+ // idempotency key in its own durable store.
+ const event = JSON.parse(req.body.toString("utf8"));
+
+ // Test deliveries verify reachability and signature handling only.
+ // Do not let placeholder test data change application state.
+ if (req.headers["x-request-network-test"] === "true") {
+ return res.status(200).send("test received");
+ }
+
await handleEvent(event, deliveryId);
return res.status(200).send("ok");
} catch (err) {
@@ -103,25 +109,27 @@ async function handleEvent(event: any, deliveryId: string) {
await markOrderPaid(event.requestId, event.txHash, deliveryId);
break;
- case "payment.partial":
- await recordPartialPayment(
- event.requestId,
- event.amount,
- event.totalAmountPaid,
+ case "client_id.linked":
+ await recordLinkedPlatform(
+ event.clientId,
+ event.destinationId,
+ event.linkId,
+ event.intentId,
deliveryId,
);
break;
- case "payment.failed":
- await flagFailedPayment(event.requestId, deliveryId);
- break;
-
- case "request.recurring":
- await onRecurringInvoice(event.originalRequestId, event.requestId, deliveryId);
+ case "kyt.screening.completed":
+ await recordScreeningResult(
+ event.paymentToken,
+ event.status,
+ event.provider,
+ deliveryId,
+ );
break;
- case "compliance.updated":
- await syncKycStatus(event.clientUserId, event.kycStatus, deliveryId);
+ case "payment.failed":
+ await flagFailedPayment(event.requestId, deliveryId);
break;
// Payer-funnel telemetry from the Secure Payment Page. Never reconcile
@@ -159,7 +167,7 @@ Webhook delivery is at least once, not exactly once. Each business operation mus
| `x-request-network-signature` | HMAC-SHA256 of the raw JSON body, hex-encoded |
| `x-request-network-delivery` | ULID — use as idempotency key |
| `x-request-network-retry-count` | `0`–`3`, current retry attempt |
-| `x-request-network-test` | `true` only for `/v1/webhook/test` deliveries |
+| `x-request-network-test` | `true` for test deliveries only |
## Retry policy
@@ -208,7 +216,7 @@ async function markOrderPaid(
### Routing by Client ID
-If your platform has many merchants, give each their own Client ID. The webhook payload includes `clientId` so you can route events to the right tenant.
+If you are an orchestrator managing multiple linked platforms, use `clientId` to identify the platform for each event.
### Slack alerts on failure
@@ -223,10 +231,6 @@ case "payment.failed":
break;
```
-### Crypto-to-fiat status tracking
-
-The `payment.processing` event includes a `subStatus` field that progresses through `initiated → pending_internal_assessment → ongoing_checks → sending_fiat → fiat_sent`. Surface this in your UI so the payee sees real-time offramp progress.
-
## Local development
Use [ngrok](https://ngrok.com) to expose localhost during development:
@@ -242,7 +246,7 @@ Local URLs (`localhost`, `127.0.0.1`) are accepted by the auth API for testing.
- Full payload schemas for every event type.
+ Delivery setup, recipient routing, headers, and documented payload examples.