Documentation Index Fetch the complete documentation index at: https://docs.request.network/llms.txt
Use this file to discover all available pages before exploring further.
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.
Webhook Configuration
Webhooks are managed programmatically via the Auth API at auth.request.network β there is no Dashboard UI for webhook CRUD. 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.
Create a webhook
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" }'
Response (201 Created):
{
"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.
Manage webhooks
All endpoints accept x-client-id and operate on the webhooks owned by that Client ID.
Method Path Purpose GET/v1/webhookList webhooks for this Client ID PUT/v1/webhook/:webhookIdToggle active / inactive DELETE/v1/webhook/:webhookIdPermanently delete POST/v1/webhook/testBody { "eventType": "payment.confirmed" } β fire a test delivery
Open the Auth API Scalar docs to call these interactively with your wallet session β signing in to the Dashboard sets the session cookie thatβs shared across all *.request.network services.
Local Development
Use ngrok to receive webhooks locally, then pass the public URL to POST /v1/webhook:
ngrok http 3000
# Use the HTTPS URL (e.g., https://abc123.ngrok.io/webhook) as the webhook URL
Event Types
Payment Events (core)
Event Description Context Primary Use payment.confirmedPayment fully completed and settled After blockchain confirmation Complete fulfillment, release goods payment.partialPartial payment received for request Installments, partial orders Update balance, allow additional payments payment.failedPayment execution failed Recurring payments, cross-chain transfers Notify failure, retry logic, pause subscriptions payment.refundedPayment has been refunded to payer Cross-chain payment failures, refund scenarios Update order status, notify customer
Payment Events (Client ID-scoped)
Emitted in addition to the core events when the originating request was created with a Client ID. Payload includes extra clientId and origin fields.
Event Description payment.confirmed.client_idSame as payment.confirmed, scoped to a Client ID payment.partial.client_idSame as payment.partial, scoped to a Client ID
Payment Events (Checkout / Secure Payment-scoped)
Emitted in addition to the core events when the request was created via a Secure Payment / checkout flow.
Event Description payment.confirmed.checkoutSame as payment.confirmed, originating from a Secure Payment link payment.partial.checkoutSame as payment.partial, originating from a Secure Payment link
Processing Events
Event Description Context Primary Use payment.processingCrypto-to-fiat payment in progress subStatus values: initiated, pending_internal_assessment, ongoing_checks, sending_fiat, fiat_sent, bouncedTrack crypto-to-fiat payment status, update UI
Request Events
Event Description Context Primary Use request.recurringNew recurring request generated Subscription renewals, scheduled payments Send renewal notifications, update billing
Compliance Events
Event Description Context Primary Use compliance.updatedKYC or agreement status changed kycStatus values: not_started, pending, approved, rejected, retry_requiredagreementStatus values: not_started, pending, completed, rejected, failedUpdate user permissions, notify status payment_detail.updatedBank account verification status updated States: approved, failed, pending Enable fiat payments, update profiles
Security Implementation
Signature Verification
Every webhook includes an HMAC SHA-256 signature in the x-request-network-signature header:
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
Each webhook request includes these headers:
Header Description Example x-request-network-signatureHMAC SHA-256 signature a1b2c3d4e5f6...x-request-network-deliveryUnique delivery ID (ULID) 01ARZ3NDEKTSV4RRFFQ69G5FAVx-request-network-retry-countCurrent retry attempt (0-3) 0x-request-network-testPresent for test webhooks truecontent-typeAlways 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
// β
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
All payment events include an explorer field linking to Request Scan for transaction details.
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)
Payment Confirmed
{
"event" : "payment.confirmed" ,
"requestId" : "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93" ,
"requestID" : "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93" ,
"paymentReference" : "0x2c3366941274c34c" ,
"explorer" : "https://scan.request.network/request/0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93" ,
"amount" : "100.0" ,
"totalAmountPaid" : "100.0" ,
"expectedAmount" : "100.0" ,
"timestamp" : "2025-10-03T14:30:00Z" ,
"txHash" : "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" ,
"network" : "ethereum" ,
"currency" : "USDC" ,
"paymentCurrency" : "USDC" ,
"isCryptoToFiat" : false ,
"subStatus" : "" ,
"paymentProcessor" : "request-network" ,
"fees" : [
{
"type" : "network" ,
"amount" : "0.02" ,
"currency" : "ETH"
}
]
}
Payment Processing
{
"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"
}
}
Payment Partial
{
"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" ,
"network" : "ethereum" ,
"currency" : "USDC" ,
"paymentCurrency" : "USDC" ,
"isCryptoToFiat" : false ,
"subStatus" : "" ,
"paymentProcessor" : "request-network" ,
"fees" : []
}
Payment Failed
{
"event" : "payment.failed" ,
"requestId" : "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93" ,
"requestID" : "0151b394e3c482c5aebaa04eb04508a8db70595470760293f1b258ed96d1fafa93" ,
"paymentReference" : "0x2c3366941274c34c" ,
"subStatus" : "insufficient_funds" ,
"paymentProcessor" : "request-network"
}
Compliance Updated
{
"event" : "compliance.updated" ,
"clientUserId" : "user_test123456789" ,
"kycStatus" : "approved" ,
"agreementStatus" : "completed" ,
"isCompliant" : true ,
"timestamp" : "2025-10-03T14:30:00Z" ,
"rawPayload" : {
"verificationLevel" : "full" ,
"documents" : "verified"
}
}
Implementation Examples
For a complete working example, see Webhook reconciliation which implements webhook handling for payment notifications.
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 rawBody = req . rawBody ;
const expectedSignature = crypto
. createHmac ( "sha256" , WEBHOOK_SECRET )
. update ( rawBody )
. digest ( "hex" );
if ( ! signature || ! 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 ;
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" });
}
});
// 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 }
);
}
}
Testing
Test deliveries
Fire a test webhook from the Auth API:
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" }'
Or call it interactively from the Auth API Scalar docs .
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:
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...
});
Best Practices
Error Handling
Implement idempotency: Use delivery IDs to prevent duplicate processing
Graceful degradation: Handle unknown event types without errors
Timeout management: Complete processing within 5 seconds
Troubleshooting
Common Issues
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
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)
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
Webhooks & Events High-level webhook concepts and workflow
Webhook reconciliation Complete webhook implementation example
Authentication API credential setup and webhook security
Request Dashboard Manage Client IDs and payment destinations (webhooks are managed via the Auth API above)