Webhook Setup

Webhooks allow your application to receive real-time notifications about payment events. Instead of polling for status changes, the payment gateway pushes event data to your configured endpoints.

How Webhooks Work

sequenceDiagram
    participant C as Customer
    participant M as Payment Gateway
    participant W as Your Webhook Endpoint
    participant D as Your Database

    C->>M: Complete Payment
    M->>M: Process Payment
    M->>W: POST Webhook Event
    W->>W: Verify Signature
    W->>D: Update Order Status
    W->>M: HTTP 200 OK

Configuring Webhooks

Webhooks are configured as part of your Payment Application setup. Each application has a single callback URL where all webhook events are delivered.

Via Dashboard

  1. Navigate to Payment Applications in your dashboard
  2. Select your application (or create a new one)
  3. Set the Callback URL field to your webhook endpoint
  4. Set a Webhook Secret for signature verification
  5. Save the application

Via API

When creating or updating a payment application, include the callbackUrl and secret fields. See the Payment Applications guide for the full API request format.

Implementing a Webhook Handler

All webhooks are delivered as POST requests with the event type, timestamp, and signature in headers. See the Events reference for the full list of events and payload structures.

const crypto = require('node:crypto');
const express = require('express');
const app = express();

app.use('/webhooks/payments', express.raw({ type: 'application/json' }));

app.post('/webhooks/payments', (req, res) => {
  const signature = req.headers['x-paymentservice-signature'];
  const timestamp = req.headers['x-paymentservice-timestamp'];
  const eventType = req.headers['x-paymentservice-event'];
  const webhookSecret = process.env.WEBHOOK_SECRET;

  // Verify signature
  const signedPayload = `${timestamp}.${req.body.toString()}`;
  const expectedSignature = crypto
    .createHmac('sha256', webhookSecret)
    .update(signedPayload)
    .digest('hex');

  if (signature !== expectedSignature) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  const payment = event.payment;

  switch (eventType) {
    case 'payment.completed':
      console.log('Payment completed:', payment.id);
      // Update order status, send confirmation email, etc.
      break;
    case 'payment.received':
      console.log('Payment received:', payment.id);
      break;
    case 'payment.underpaid':
      console.log('Payment underpaid:', payment.id);
      break;
    case 'payment.refund':
      console.log('Payment refunded:', payment.id);
      break;
  }

  res.status(200).send('OK');
});

app.listen(3000);

Webhook Response Requirements

Your endpoint must return a 2xx HTTP status code within 10 seconds. If the request times out or returns a non-2xx response, the webhook will be retried. See Retry Logic for details.

Testing Webhooks Locally

Use a tunneling tool like ngrok to expose your local development server:

ngrok http 3000

# Use the generated URL as your callback URL:
# https://abc123.ngrok.io/webhooks/payments

Best Practices

  1. Always verify signatures to ensure webhook authenticity
  2. Respond quickly — return 200 immediately, process asynchronously if needed
  3. Implement idempotency — handle duplicate events gracefully
  4. Log all events for debugging and audit trails
  5. Use HTTPS for your webhook endpoint

Next Steps