diff --git a/api-reference/webhooks.mdx b/api-reference/webhooks.mdx
index 3648cc5..38aa0f8 100644
--- a/api-reference/webhooks.mdx
+++ b/api-reference/webhooks.mdx
@@ -1,13 +1,13 @@
---
title: "Webhooks"
-description: "Complete webhook implementation guide with event types, security, and retry configuration"
+description: "Configure platform Client ID and orchestrator webhooks, including event delivery, signatures, and retries."
---
## 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 deliver HMAC-signed notifications for platform Client ID and orchestrator flows. Each endpoint is configured separately and can receive automatic retries.
-## Webhook Configuration
+## Platform Client ID webhooks
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.
@@ -52,6 +52,89 @@ ngrok http 3000
# Use the HTTPS URL (e.g., https://abc123.ngrok.io/webhook) as the webhook URL
```
+## Orchestrator webhooks
+
+Orchestrator webhooks are owned by your orchestrator. Register and manage them with `x-orchestrator-key`, not a platform's `x-client-id`.
+
+Register an endpoint before you send a hosted onboarding URL to a platform. Its active endpoints receive:
+
+- [`client_id.linked`](#client-id-linked) after the platform completes hosted onboarding.
+- [`kyt.screening.completed`](#kyt-screening-completed) after an orchestrator-linked payment reaches a definitive screening result.
+
+### Register an endpoint
+
+```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 endpoint 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 endpoint. 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 [Signature Verification](#signature-verification). Webhook deliveries may be retried, so your endpoint must safely handle the same notification more than once.
+
+### Test your endpoint
+
+Send a signed mock event to every active endpoint:
+
+```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" }'
+```
+
+```json
+{
+ "sent": 1,
+ "failed": 0
+}
+```
+
+Test deliveries include `x-request-network-test: true`. They use the same signing process and payload shape as a real event, with placeholder values.
+
+### Manage 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"
+```
+
+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.
+
## Event Types
@@ -214,7 +297,7 @@ All payment events include an `explorer` field linking to [Request Scan](https:/
- `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.
-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).
+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](#orchestrator-webhooks).
### Client ID linked
@@ -399,133 +482,9 @@ Use `intentId` or `externalId` to match this event to your onboarding record. Us
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";
+## Process webhook deliveries
-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 }
- );
- }
-}
-```
-
-
+For a signature-verifying handler that safely acknowledges test deliveries and retries, see [Webhook reconciliation](/use-cases/webhook-reconciliation).
## Testing
@@ -542,23 +501,7 @@ curl -X POST "https://auth.request.network/v1/webhook/test" \
Or call it interactively from the [Auth API Scalar docs](https://auth.request.network/open-api/#tag/webhook/POST/v1/webhook/test).
-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.
-
-### Test Webhook Identification
-Test webhooks include the `x-request-network-test: true` header:
-
-```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...
-});
-```
+Test deliveries arrive at all active webhooks for that Client ID and include the `x-request-network-test: true` header. They use placeholder data, so acknowledge a verified test delivery without changing application state. See [Webhook reconciliation](/use-cases/webhook-reconciliation) for a safe handler pattern.
## Best Practices
@@ -591,15 +534,15 @@ app.post("/webhook", (req, res) => {
## Related Documentation
-
+
High-level webhook concepts and workflow
-
+
Complete webhook implementation example
-
+
API credential setup and webhook security
diff --git a/docs.json b/docs.json
index 9dd938f..e1eb4c3 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": {
@@ -546,10 +550,10 @@
"group": "Orchestrators",
"pages": [
"orchestrators/overview",
+ "orchestrators/webhooks",
"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..951e5c9 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#orchestrator-webhooks) 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#client-id-linked) 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..6bb23b3 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..0075e3e 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 `client_id.linked` notifications after a platform completes hosted onboarding and `kyt.screening.completed` notifications after a definitive screening result. See [Orchestrator webhooks](/api-reference/webhooks#orchestrator-webhooks).
- **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), [Orchestrator webhooks](/api-reference/webhooks#orchestrator-webhooks), and [Orchestrator fees](/orchestrators/fees) for details.
## How orchestrators apply at payment time
@@ -91,7 +91,7 @@ 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.
diff --git a/orchestrators/webhooks.mdx b/orchestrators/webhooks.mdx
index 29ce9a1..cabd8f6 100644
--- a/orchestrators/webhooks.mdx
+++ b/orchestrators/webhooks.mdx
@@ -1,177 +1,4 @@
---
title: "Orchestrator webhooks"
-description: "Receive signed notifications when a platform completes hosted onboarding or a transaction screening decision is reached."
+url: "/api-reference/webhooks#orchestrator-webhooks"
---
-
-## 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.
-
-