Skip to main content

Why Webhooks?

Get real-time notifications when payments complete, fail, or change status. Webhooks keep your system in sync without polling.

Instant Updates

Know immediately when payments complete or fail

Reliable Delivery

Automatic retries ensure you never miss events

Efficient

No need to constantly poll for status updates

Complete Information

Each webhook includes full payment details

Setup

1

Create an Endpoint

Create a route on your server to receive webhook POST requests.
2

Register Your URL

Contact our support team to register your webhook endpoint.
3

Verify Signatures

Always validate that webhooks come from Cheqpay (see security below).
Contact [email protected] to register your webhook endpoint URL.

Webhook Events

EventTriggered WhenRecommended Action
payment.completedPayment successfulFulfill order
payment.failedPayment declinedNotify customer, offer retry
payment.refundedFull refund processedCancel order, update records
payment.partially_refundedPartial refund issuedUpdate order amount
spei.deposit_receivedSPEI transfer receivedProcess and fulfill order

Webhook Format

Each webhook includes event details and payment information:
{
  "eventId": "evt_abc123",
  "eventType": "payment.completed",
  "timestamp": "2025-10-30T14:30:00.000Z",
  "data": {
    "paymentOrderId": "ord_xyz789",
    "paymentId": "pay_def456",
    "externalId": "order-12345",
    "status": "COMPLETED",
    "amount": 10000,
    "currency": "MXN",
    "customerId": "cus_ghi012",
    "customer": {
      "firstName": "María",
      "lastName": "González",
      "email": "[email protected]"
    },
    "paymentMethod": {
      "type": "card",
      "card": {
        "brand": "visa",
        "last4": "1111"
      }
    }
  }
}

Common Fields

FieldTypeDescription
eventIdstringUnique event identifier
eventTypestringType of event
timestampstringISO 8601 timestamp
dataobjectEvent-specific data
data.paymentOrderIdstringCheqpay payment order ID
data.externalIdstringYour order ID
data.statusstringCurrent payment status

Handle Webhooks Properly

Requirements

Return 200 OK within 5 seconds. Process data asynchronously.
Always check authenticity before processing events.
Queue work for background processing. Don’t block the response.
Events may be sent multiple times. Make your handler idempotent.

Example Handler (Node.js)

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

app.post('/webhooks/cheqpay', express.json(), (req, res) => {
  // 1. Verify signature first
  const signature = req.headers['x-cheqpay-signature'];
  if (!verifySignature(req.body, signature)) {
    return res.status(401).send('Invalid signature');
  }
  
  // 2. Respond immediately
  res.status(200).send('OK');
  
  // 3. Process in background
  processWebhook(req.body);
});

function processWebhook(webhook) {
  // Queue for async processing
  queue.add('process-payment', {
    eventId: webhook.eventId,
    eventType: webhook.eventType,
    data: webhook.data
  });
}

Example Handler (Python)

from flask import Flask, request
import hmac
import hashlib

app = Flask(__name__)

@app.route('/webhooks/cheqpay', methods=['POST'])
def webhook():
    # 1. Verify signature
    signature = request.headers.get('X-Cheqpay-Signature')
    if not verify_signature(request.data, signature):
        return 'Invalid signature', 401
    
    # 2. Respond immediately
    # 3. Process async
    webhook_data = request.json
    queue.enqueue(process_webhook, webhook_data)
    
    return 'OK', 200

def process_webhook(data):
    event_type = data['eventType']
    
    if event_type == 'payment.completed':
        fulfill_order(data['data']['externalId'])
    elif event_type == 'payment.failed':
        notify_customer(data['data'])
    # ... handle other events

Security

Verify every webhook to ensure it’s from Cheqpay.

Signature Verification

const crypto = require('crypto');

function verifySignature(payload, signature) {
  const expected = crypto
    .createHmac('sha256', YOUR_WEBHOOK_SECRET)
    .update(JSON.stringify(payload))
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Python Example

import hmac
import hashlib
import json

def verify_signature(payload, signature):
    expected = hmac.new(
        YOUR_WEBHOOK_SECRET.encode(),
        json.dumps(payload).encode(),
        hashlib.sha256
    ).hexdigest()
    
    return hmac.compare_digest(signature, expected)
Your webhook secret is provided when you register your endpoint. Keep it secure and never commit it to version control.

Handle Duplicate Events

Webhooks may be delivered multiple times. Make your handler idempotent:
async function processPaymentCompleted(webhook) {
  const eventId = webhook.eventId;
  
  // Check if already processed
  const existing = await db.webhookEvents.findOne({ eventId });
  if (existing) {
    console.log('Event already processed:', eventId);
    return;
  }
  
  // Process the event
  await fulfillOrder(webhook.data.externalId);
  
  // Mark as processed
  await db.webhookEvents.insert({ 
    eventId,
    processedAt: new Date()
  });
}

Event-Specific Handling

Payment Completed

if (webhook.eventType === 'payment.completed') {
  const { externalId, amount, customerId } = webhook.data;
  
  // Fulfill the order
  await orders.fulfill(externalId);
  
  // Send confirmation email
  await sendEmail(customerId, {
    subject: 'Payment Received',
    template: 'payment-confirmation'
  });
  
  // Update inventory
  await inventory.reduce(externalId);
}

Payment Failed

if (webhook.eventType === 'payment.failed') {
  const { externalId, customerId } = webhook.data;
  
  // Notify customer
  await sendEmail(customerId, {
    subject: 'Payment Failed',
    template: 'payment-failed',
    data: { orderId: externalId }
  });
  
  // Update order status
  await orders.update(externalId, { status: 'payment_failed' });
}

SPEI Deposit Received

if (webhook.eventType === 'spei.deposit_received') {
  const { paymentOrderId, externalId, amount } = webhook.data;
  
  // Verify amount matches
  const order = await orders.findById(externalId);
  if (order.amount === amount) {
    // Fulfill order
    await orders.fulfill(externalId);
    
    // Send confirmation
    await sendEmail(order.customerId, {
      subject: 'SPEI Transfer Received',
      template: 'spei-confirmed'
    });
  }
}

Automatic Retries

If your endpoint is unavailable, Cheqpay retries automatically:
1

Initial Attempt

Webhook is sent to your endpoint.
2

First Retry

If it fails, retry after 1 minute.
3

Second Retry

If still failing, retry after 5 minutes.
4

Final Retry

Last attempt after 30 minutes.
5

Notification

If all retries fail, you’ll be notified via email.
Set up monitoring to alert you of webhook failures before all retries are exhausted.

Testing Locally

Use ngrok to test webhooks during development:
# Install ngrok
npm install -g ngrok

# Start your local server
node server.js  # Running on port 3000

# Expose to internet
ngrok http 3000

# Output:
# Forwarding: https://abc123.ngrok.io -> http://localhost:3000
Register the ngrok URL with Cheqpay:
https://abc123.ngrok.io/webhooks/cheqpay
Now you can test webhooks with your local development server!
Remember to update to your production URL before going live.

Monitoring and Debugging

Log All Webhooks

app.post('/webhooks/cheqpay', (req, res) => {
  // Log webhook for debugging
  logger.info('Webhook received', {
    eventId: req.body.eventId,
    eventType: req.body.eventType,
    timestamp: req.body.timestamp,
    signature: req.headers['x-cheqpay-signature']
  });
  
  // ... rest of handler
});

Monitor Webhook Health

Track webhook delivery and processing:
const metrics = {
  received: 0,
  processed: 0,
  failed: 0,
  averageProcessingTime: 0
};

async function processWebhook(webhook) {
  metrics.received++;
  const start = Date.now();
  
  try {
    await handleWebhook(webhook);
    metrics.processed++;
  } catch (error) {
    metrics.failed++;
    logger.error('Webhook processing failed', { error, webhook });
  }
  
  const duration = Date.now() - start;
  metrics.averageProcessingTime = 
    (metrics.averageProcessingTime + duration) / 2;
}

Best Practices

Always use HTTPS endpoints. Cheqpay will not send webhooks to HTTP URLs.
Never skip signature verification. This protects against spoofed webhooks.
Respond within 5 seconds. Use background jobs for actual processing.
Use eventId to track processed events and avoid duplicate processing.
Set up alerts for webhook failures. Investigate and fix issues quickly.
Log all webhook events for debugging and audit trails.

Webhook Endpoint Requirements

HTTPS Required

Must use secure HTTPS connection

Public Access

Endpoint must be publicly accessible

Fast Response

Return 200 OK within 5 seconds

Signature Verification

Verify signatures on all requests

Next Steps