KuvarPay API
Home
Home
  1. Home
  • Overview
  • SDK Integration Guide
  • Webhooks Integration Guide
  • Subscriptions Guide
  • Transactions
    • Calculate required crypto amount for a fiat target
      POST
    • Create a new fiat transaction
      POST
    • Get transaction by ID
      GET
    • List transactions
      GET
    • Public transaction status by reference
      GET
    • Demo pay (sandbox/testing)
      POST
  • Checkout Sessions
    • Create a checkout session (returns authToken for SSE)
      POST
    • Get a checkout session
      GET
    • Receive payment status webhook for session
      POST
  • SSE
    • Subscribe to transaction updates (SSE)
      GET
    • Subscribe to session updates (SSE)
      GET
  • Currencies
    • List supported currencies
      GET
  • Sandbox
    • Simulate sandbox error scenarios for a transaction
      POST
    • Deliver a test webhook to a URL
      POST
    • List available sandbox error scenarios
      GET
    • Get sandbox environment info
      GET
    • Reset all sandbox data for your business (destructive)
      DELETE
  • Sandbox Simulator
    • Start the sandbox transaction simulator
      POST
    • Stop the sandbox transaction simulator
      POST
    • Get current status of the sandbox transaction simulator
      GET
    • Update simulator configuration
      PUT
    • Force execute a specific scenario for a transaction (testing only)
      POST
    • Force execute a specific scenario for a transaction (URL param)
      POST
    • List pending sandbox transactions available for simulation
      GET
    • Reset sandbox transactions to PENDING for testing
      POST
  • Invoices
    • Create a new invoice
    • Send invoice email to customer
  • Payment Links
    • Create a new payment link
  • Subscriptions
    • Create subscription checkout session
    • Get a subscription checkout session by UID
    • Confirm subscription checkout session
    • Get stablecoin currencies supported for subscriptions
    • Submit relay authorization for subscription
    • Create a new subscription
    • Get a subscription by UID
    • Get relay authorization status for a subscription
    • Update a subscription
    • Cancel a subscription
    • Create a new subscription plan
    • List subscription plans
    • Get a subscription plan by UID
    • Update a subscription plan
    • Create a price for a subscription plan
    • Get prices for a subscription plan
    • Update a subscription price
    • Create a subscription invoice
    • List subscription invoices
    • Create a metered subscription invoice
    • Get a subscription invoice by UID
    • Charge a subscription invoice
    • Get charge attempts for an invoice
  • Payment Fiat Rates
    • Get fiat rate for a specific currency
    • Get multiple fiat rates
    • Update NGN rate from CryptoCompare
    • Update all supported fiat rates
    • Get Flutterwave transfer rates (debug/testing)
    • List supported bank transfer and mobile money currencies
    • Clear the fiat rates cache
    • Get cache statistics
  • Transfer Fees
    • Get transfer fee for a specific amount and currency
    • Get optimal transfer fee (bank vs mobile money)
    • Get all stored transfer fees
    • Manually update transfer fees from Flutterwave
    • Get currencies supported for transfers
  • Schemas
    • Schemas
      • CalculatePaymentRequest
      • CalculatePaymentResponse
      • CreateTransactionRequest
      • Transaction
      • CreateTransactionResponse
      • CheckoutSessionCreateRequest
      • CheckoutSession
      • CheckoutSessionDetails
      • CheckoutSessionGetResponse
      • PaymentStatusWebhookPayload
      • SimpleAck
      • ErrorResponse
      • CurrenciesResponse
      • SandboxDeliveryResult
      • SandboxSimulateErrorRequest
      • SandboxSimulateErrorResponse
      • SandboxTestWebhookRequest
      • SandboxTestWebhookResponse
      • SandboxErrorScenariosResponse
      • SandboxInfoResponse
      • SandboxResetRequest
      • SandboxResetResponse
      • CreateInvoiceRequest
      • CreateSubscriptionCheckoutSessionRequest
      • InvoiceResponse
      • CreateSubscriptionCheckoutSessionResponse
      • CreateInvoiceResponse
      • ConfirmSubscriptionCheckoutSessionRequest
      • SendInvoiceEmailRequest
      • ConfirmSubscriptionCheckoutSessionResponse
      • SendInvoiceEmailResponse
      • SubscriptionCurrency
      • CreatePaymentLinkRequest
      • SubscriptionCurrenciesResponse
      • PaymentLinkResponse
      • CreatePaymentLinkResponse
      • CreateSubscriptionRequest
      • Subscription
      • UpdateSubscriptionRequest
      • SubscriptionResponse
      • CreatePlanRequest
      • UpdatePlanRequest
      • SubscriptionPlan
      • CreatePriceRequest
      • UpdatePriceRequest
      • SubscriptionPrice
      • PlanResponse
      • PlansListResponse
      • PriceResponse
      • PricesListResponse
      • SubscriptionInvoice
      • ChargeInvoiceRequest
      • ChargeAttempt
      • InvoicesListResponse
      • ChargeResponse
      • ChargeAttemptsListResponse
      • SubscriptionCheckoutSession
      • SubscriptionCheckoutSessionResponse
  1. Home

Webhooks Integration Guide

KuvarPay Webhooks Guide#

This guide explains how to receive, verify, and process webhooks sent from the KuvarPay Business Platform.
Webhooks allow KuvarPay to notify your backend in real time when events happen (e.g., payments completed, withdrawals processed). Your server exposes an HTTPS endpoint; KuvarPay delivers JSON payloads via POST requests to that endpoint.

1) Configure Your Webhook Endpoint#

1.
Deploy an HTTPS endpoint that accepts POST requests with a JSON body (application/json).
2.
In the KuvarPay dashboard, add your endpoint URL and obtain your Webhook Secret.
3.
Save the Webhook Secret securely (environment variable). You will use it to verify request signatures.
4.
Optionally enable/disable specific event types you want to receive.
Tip: For local development, use a tunneling service (e.g., ngrok) to expose your local server and register the tunnel URL in the dashboard.

2) Delivery Format#

Method: POST
Content-Type: application/json
Timeout: Your endpoint should respond within 30 seconds. KuvarPay will retry failed deliveries with exponential backoff.
Retries: Duplicate deliveries may occur due to retries; ensure your processing is idempotent using the delivery ID header.

Request Headers#

X-KuvarPay-Signature: HMAC-SHA256 signature in the format sha256=<hex_signature>
X-KuvarPay-Event: The event type (e.g., payment.completed, webhook.test)
X-KuvarPay-Delivery: Unique delivery ID for idempotency tracking
User-Agent: KuvarPay webhook user agent
Content-Type: application/json

Signature Verification#

The signature is calculated as:
HMAC-SHA256(webhook_secret, raw_json_payload)
Best practices:
Always verify the signature before processing the event.
Use the raw, unmodified request body for HMAC calculation (not a re-serialized JSON string).
Compare signatures using a constant-time comparison to avoid timing attacks.
Reject requests with missing/invalid signatures with HTTP 401.

3) Events#

Common event types include:
checkout_session.completed - Sent when a checkout session is completed
checkout_session.failed - Sent when a checkout session fails
checkout_session.expired - Sent when a checkout session expires
checkout_session.payment_received - Sent when payment is received for a session
checkout_session.payment_partial - Sent when partial payment is received for a session
checkout_session.payment_excess - Sent when excess payment is received for a session
checkout_session.compliance_hold - Sent when a session is held for compliance review
checkout_session.status_updated - Sent when session status changes
checkout_session.updated - Sent for general session updates

Invoice Events#

These events are related to invoice processing:
invoice.paid - Sent when an invoice is paid

Payment Link Events#

These events are related to payment link processing:
payment_link.paid - Sent when a payment link is paid

Subscription Events#

These events are related to subscription lifecycle management:
subscription.created - Sent when a new subscription is created
subscription.updated - Sent when subscription details are modified
subscription.cancelled - Sent when a subscription is cancelled
subscription.activated - Sent when a subscription becomes active
subscription.trial_ended - Sent when trial period ends
subscription.payment_failed - Sent when payment attempts fail

Subscription Invoice Events#

These events are related to subscription invoice processing:
subscription_invoice.created - Sent when a new subscription invoice is generated
subscription_invoice.paid - Sent when a subscription invoice is successfully paid
subscription_invoice.payment_failed - Sent when subscription invoice payment fails

Test Events#

These events are used for testing webhook endpoints:
webhook.test - Sent for testing webhook endpoint connectivity
Note: The exact payload shape varies by event. Use the event type to route to the correct handler in your code.

4) Example Payloads#

Test webhook:
{
  "event": "webhook.test",
  "timestamp": "2024-01-01T00:00:00Z"
}
Payment completed:
{
  "id": "pay_1234567890",
  "event": "payment.completed",
  "amount": "100.00",
  "currency": "USD",
  "status": "completed",
  "customer_id": "cust_1234567890",
  "timestamp": "2024-01-01T00:00:00Z"
}
Subscription created:
{
  "subscription": {
    "id": 123,
    "uid": "sub_1234567890",
    "customerId": "cust_1234567890",
    "priceId": "price_1234567890",
    "status": "active",
    "currentPeriodStart": "2024-01-01T00:00:00Z",
    "currentPeriodEnd": "2024-02-01T00:00:00Z",
    "trialStart": null,
    "trialEnd": null,
    "cancelAtPeriodEnd": false,
    "canceledAt": null,
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-01T00:00:00Z"
  }
}
Subscription invoice created:
{
  "subscription_invoice": {
    "id": 456,
    "uid": "inv_1234567890",
    "subscriptionId": "sub_1234567890",
    "amount": "29.99",
    "currency": "USD",
    "status": "pending",
    "invoiceId": "invoice_1234567890",
    "periodStart": "2024-01-01T00:00:00Z",
    "periodEnd": "2024-02-01T00:00:00Z",
    "dueDate": "2024-01-08T00:00:00Z",
    "nextPaymentAttempt": "2024-01-01T12:00:00Z",
    "createdAt": "2024-01-01T00:00:00Z",
    "updatedAt": "2024-01-01T00:00:00Z"
  }
}

5) Implementation Examples#

Below are minimal examples demonstrating signature verification and idempotency.

Node.js (Express)#

import express from 'express';
import crypto from 'crypto';

const app = express();
const PORT = process.env.PORT || 3001;
const WEBHOOK_SECRET = process.env.RAYSWAP_WEBHOOK_SECRET || '';

// Capture raw body for HMAC verification
app.use('/webhook', express.raw({ type: 'application/json' }));

function verifySignature(rawBody: Buffer, signatureHeader?: string): boolean {
  if (!signatureHeader || !WEBHOOK_SECRET) return false;
  const [algo, expectedSig] = signatureHeader.split('=');
  if (algo !== 'sha256' || !expectedSig) return false;

  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  hmac.update(rawBody);
  const digest = hmac.digest('hex');

  // Constant-time comparison
  const a = Buffer.from(digest, 'utf8');
  const b = Buffer.from(expectedSig, 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Simple in-memory idempotency store (replace with persistent storage)
const processedDeliveries = new Set<string>();

app.post('/webhook', (req, res) => {
  const rawBody = req.body as Buffer;
  const signature = req.header('X-KuvarPay-Signature');
  const event = req.header('X-KuvarPay-Event');
  const deliveryId = req.header('X-KuvarPay-Delivery') || '';

  if (!verifySignature(rawBody, signature)) {
    return res.status(401).send('Invalid signature');
  }

  if (processedDeliveries.has(deliveryId)) {
    return res.status(200).send('Duplicate delivery');
  }

  let payload: unknown;
  try {
    payload = JSON.parse(rawBody.toString('utf8'));
  } catch (e) {
    return res.status(400).send('Invalid JSON');
  }

  // TODO: handle event types (e.g., payment.completed)
  // route by `event` header or by `payload.event`

  processedDeliveries.add(deliveryId);
  return res.status(200).send('ok');
});

app.get('/health', (_req, res) => res.send('ok'));

app.listen(PORT, () => console.log(`Webhook server listening on ${PORT}`));

6) Testing#

From the KuvarPay dashboard, use the “Send test webhook” or similar feature to trigger webhook.test to your endpoint.
Use cURL for manual testing:
For local development, run the Node sample and expose it via a tunnel, then set that public URL in the dashboard.

7) Idempotency and Retries#

Use X-KuvarPay-Delivery as a unique key to detect duplicate deliveries.
Store processed delivery IDs and skip re-processing when a duplicate is received.
KuvarPay retries failed deliveries with exponential backoff.

8) Response Codes#

2xx: Acknowledged (processing succeeded or will be handled asynchronously)
4xx: Client errors (e.g., 400 for invalid JSON, 401 for invalid signature)
5xx: Server errors; KuvarPay will retry

9) Troubleshooting#

Invalid signature: Ensure you use the exact raw request body and correct Webhook Secret.
JSON parse errors: Read the raw body before parsing; don’t modify the body string prior to HMAC calculation.
Timeouts: Respond within 30 seconds; consider acknowledging and deferring heavy work to background jobs.
Duplicate events: Implement idempotency using delivery IDs.
Modified at 2025-09-15 18:42:19
Previous
SDK Integration Guide
Next
Subscriptions Guide
Built with