Skip to main content

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.
MethodPathPurpose
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

See Payload Examples below for detailed webhook structures.

Payment Events (core)

EventDescriptionContextPrimary Use
payment.confirmedPayment fully completed and settledAfter blockchain confirmationComplete fulfillment, release goods
payment.partialPartial payment received for requestInstallments, partial ordersUpdate balance, allow additional payments
payment.failedPayment execution failedRecurring payments, cross-chain transfersNotify failure, retry logic, pause subscriptions
payment.refundedPayment has been refunded to payerCross-chain payment failures, refund scenariosUpdate 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.
EventDescription
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.
EventDescription
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

EventDescriptionContextPrimary Use
payment.processingCrypto-to-fiat payment in progresssubStatus values: initiated, pending_internal_assessment, ongoing_checks, sending_fiat, fiat_sent, bouncedTrack crypto-to-fiat payment status, update UI

Request Events

EventDescriptionContextPrimary Use
request.recurringNew recurring request generatedSubscription renewals, scheduled paymentsSend renewal notifications, update billing

Compliance Events

EventDescriptionContextPrimary Use
compliance.updatedKYC or agreement status changedkycStatus values: not_started, pending, approved, rejected, retry_required
agreementStatus values: not_started, pending, completed, rejected, failed
Update user permissions, notify status
payment_detail.updatedBank account verification status updatedStates: approved, failed, pendingEnable 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

Request Headers

Each webhook request includes these headers:
HeaderDescriptionExample
x-request-network-signatureHMAC SHA-256 signaturea1b2c3d4e5f6...
x-request-network-deliveryUnique delivery ID (ULID)01ARZ3NDEKTSV4RRFFQ69G5FAV
x-request-network-retry-countCurrent retry attempt (0-3)0
x-request-network-testPresent for test webhookstrue
content-typeAlways JSONapplication/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" });
  }
});

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

Performance

  • 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)