Skip to content

Webhooks

Receive real-time payment notifications via webhooks.

Overview

Nivatio sends webhook events to your configured URL whenever a payment status changes. This allows you to: - Fulfill orders automatically when payments succeed - Handle failed payments gracefully - Keep your database in sync with payment status


Configuring Webhooks

  1. Log in to Dashboard
  2. Navigate to ProjectsYour ProjectWebhooks
  3. Click Add Webhook Endpoint
  4. Enter your endpoint URL (must be HTTPS)
  5. Select events to subscribe to
  6. Save the configuration

Webhook URL Requirements

  • Must use HTTPS in production
  • Must return a 200 status code within 5 seconds
  • Publicly accessible (not localhost)

Webhook Events

Event Type Description When Sent
payment.succeeded Payment confirmed on-chain After transaction confirmation
payment.failed Payment failed or was cancelled When payment fails
payment.pending Order created, awaiting payment When order is created
payment.expired Payment expired (timeout) After timeout period

Webhook Payload

Example payment.succeeded event:

{
  "event": "payment.succeeded",
  "timestamp": 1713888000,
  "data": {
    "id": "order_abc123",
    "amount": 100000,
    "currency": "NUSD",
    "status": "PAID",
    "txHash": "0xabc123...",
    "blockNumber": 1234567,
    "confirmations": 2,
    "paidAt": "2026-05-06T18:29:45Z",
    "metadata": {
      "orderId": "internal_123"
    }
  }
}

Handling Webhooks

Example: Express.js

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
  const event = req.body;

  // Verify event structure
  if (!event.event || !event.data) {
    return res.status(400).json({ error: 'Invalid payload' });
  }

  // Handle the event
  switch (event.event) {
    case 'payment.succeeded':
      handleSuccessfulPayment(event.data);
      break;
    case 'payment.failed':
      handleFailedPayment(event.data);
      break;
    default:
      console.log(`Unhandled event type: ${event.event}`);
  }

  // Acknowledge receipt
  res.json({ received: true });
});

function handleSuccessfulPayment(order) {
  console.log(`Payment ${order.id} succeeded!`);
  console.log(`Transaction: ${order.txHash}`);
  // Fulfill the order in your system
}

app.listen(3000, () => console.log('Webhook listener running on port 3000'));

Example: Python (Flask)

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    event = request.json

    if event['event'] == 'payment.succeeded':
        handle_successful_payment(event['data'])

    return jsonify({'received': True})

def handle_successful_payment(order):
    print(f"Payment {order['id']} succeeded!")
    # Fulfill the order

if name == 'main':
    app.run(port=3000)

Example: Python (Flask)

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    event = request.json

    if event['type'] == 'payment.succeeded':
        handle_successful_payment(event['data'])

    return jsonify({'received': True})

def handle_successful_payment(order):
    print(f"Payment {order['id']} succeeded!")
    # Fulfill the order

if __name__ == '__main__':
    app.run(port=3000)

Idempotency & Retries

Implementing Idempotency

The webhook payload includes an eventId field for deduplication:

const processedEvents = new Set();

app.post('/webhook', (req, res) => {
  const event = req.body;

  // Check if already processed
  if (processedEvents.has(event.eventId)) {
    return res.json({ received: true, reason: 'duplicate' });
  }

  // Process event...

  // Mark as processed
  processedEvents.add(event.eventId);

  res.json({ received: true });
});

Retry Mechanism

Nivatio retries failed webhooks: - Initial delay: Immediate (t=0) - Backoff: 500ms, then 1000ms - Max retries: 3 attempts total - Timeout: 5 seconds per attempt

Attempt Delay from Start
1 Immediate (t=0)
2 +500ms (t=500ms)
3 +1000ms (t=1500ms total)

Webhook Timeouts

If your endpoint doesn't respond within 5 seconds, the delivery is marked as failed and retried.


Testing Webhooks

Using Sandbox

  1. Configure webhook URL in sandbox project
  2. Create a test order
  3. Simulate payment using internal key:
    curl -X POST https://sandbox.nivat.io/v1/sandbox/simulate-pay \
      -H "x-nivatio-internal-key: YOUR_INTERNAL_KEY" \
      -H "Content-Type: application/json" \
      -d '{"orderId": "order_abc123"}'
    
  4. Check your endpoint receives the webhook

Local Testing with ngrok

# Expose local server to internet
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# e.g., https://abc123.ngrok.io/webhook

Security Best Practices

  1. Use HTTPS - Never accept webhooks over HTTP in production
  2. Validate payload - Check required fields before processing
  3. Implement idempotency - Use eventId to prevent duplicates
  4. Respond quickly - Return 200 within 5 seconds
  5. Log all events - Keep audit trail for debugging
  6. Verify transaction on-chain - For high-value orders, verify the txHash

Troubleshooting

Issue Solution
Webhook not received Check URL is accessible, uses HTTPS, returns 200
Duplicate events Implement idempotency with eventId
Timeout errors Optimize endpoint response time
Invalid payload Verify webhook signature using x-nivatio-signature

Next Steps