KuvarPay API
Home
Home
  1. Home
  • Overview
  • SDK Integration Guide
  • Webhooks Integration Guide
  • Subscriptions Guide
  • Transactions
    • Calculate Payment Amount
      POST
    • Create Transaction
      POST
    • Get Transactions Details
      GET
    • Get Transactions
      GET
  • Checkout Sessions
    • Create Checkout Session
      POST
    • Get Checkout Session
      GET
  • Transfer Fees
    • /api/v1/optimal-transfer-fee
      GET
  • Sandbox Simulator
    • Force Simulator Scenario
      POST
    • Force Transaction Status
      POST
  • Invoices
    • Create a new invoice
      POST
    • Send invoice email to customer
      POST
  • Payment Links
    • Create a new payment link
      POST
  • Server-Sent Events (SSE)
    • Get Transaction Details
      GET
    • Get Session Details
      GET
  • Subscriptions
    • Create Plans
      POST
    • Get Plans
      GET
    • Get Plans Details
      GET
    • Update Plans Details
      PATCH
    • Create Prices
      POST
    • Get Prices
      GET
    • Update Prices Details
      PATCH
    • Create Checkout Sessions
      POST
    • Get Checkout Sessions Details
      GET
    • Confirm Subscription Checkout
      POST
    • Create Subscriptions
      POST
    • Get Subscriptions
      GET
    • Get Subscriptions Details
      GET
    • Update Subscriptions Details
      PATCH
    • Delete Subscriptions Details
      DELETE
    • Renew Subscription
      POST
    • Confirm Subscription Cancellation
      POST
    • Create Subscription Invoices
      POST
    • Upgrade Subscription
      POST
    • Get Subscription Invoices
      GET
    • Downgrade Subscription
      POST
    • Renew Subscription Authorization
      POST
    • Create Metered Invoices
      POST
    • Get Subscription Invoices Details
      GET
    • Get Charge Attempts
      GET
    • Create Authorizations
      POST
    • Revoke Relay Authorization
      POST
    • Get Relay Authorization Status
      GET
  • Currencies
    • Get Subscription Currencies
    • Get Supported Currencies
    • Get networks supported for subscription payments
    • Get Currencies
    • Get all supported networks
  • Schemas
    • ErrorResponse
    • CreateInvoiceRequest
    • CreatePaymentLinkRequest
    • InvoiceResponse
    • PaymentLinkResponse
    • CreateInvoiceResponse
    • CreatePaymentLinkResponse
    • SendInvoiceEmailRequest
    • SendInvoiceEmailResponse
    • SubscriptionInvoice
Home
Home
  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