Webhook Integration
Complete guide to handling ShadhinPay webhooks
Webhook Integration Guide
This guide walks you through setting up webhook handlers to receive real-time payment notifications.
Overview
Webhooks are HTTP POST requests that ShadhinPay sends to your server when events occur. They enable you to:
- Update order status in real-time
- Send confirmation emails to customers
- Trigger fulfillment workflows
- Keep your database in sync
Setting Up Your Webhook Endpoint
Step 1: Create an Endpoint
Create a POST endpoint that can receive webhook requests:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.post('/webhooks/shadhinpay', async (req, res) => {
// 1. Verify signature
const signature = req.headers['x-shadhinpay-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Invalid signature');
}
// 2. Process the event
const { event, data } = req.body;
try {
await handleWebhookEvent(event, data);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Error processing webhook');
}
});
function verifySignature(payload, signature) {
const expected = crypto
.createHmac('sha256', process.env.SHADHINPAY_SECRET_KEY)
.update(JSON.stringify(payload))
.digest('hex');
return `sha256=${expected}` === signature;
}
async function handleWebhookEvent(event, data) {
switch (event) {
case 'payment.completed':
await fulfillOrder(data.payment_id, data.metadata.order_id);
break;
case 'payment.failed':
await notifyCustomerOfFailure(data.customer_phone);
break;
case 'payment.refunded':
await processRefund(data.payment_id);
break;
}
}
app.listen(3000);from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import os
app = Flask(__name__)
@app.route('/webhooks/shadhinpay', methods=['POST'])
def handle_webhook():
# 1. Verify signature
signature = request.headers.get('X-ShadhinPay-Signature')
if not verify_signature(request.json, signature):
return 'Invalid signature', 401
# 2. Process the event
event = request.json['event']
data = request.json['data']
try:
handle_event(event, data)
return 'OK', 200
except Exception as e:
print(f'Webhook error: {e}')
return 'Error', 500
def verify_signature(payload, signature):
secret = os.environ['SHADHINPAY_SECRET_KEY']
expected = hmac.new(
secret.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
return f'sha256={expected}' == signature
def handle_event(event, data):
if event == 'payment.completed':
fulfill_order(data['payment_id'])
elif event == 'payment.failed':
notify_failure(data['customer_phone'])Step 2: Register Your Endpoint
When creating payments, include your webhook URL:
const payment = await createPayment({
amount: 1000,
currency: 'BDT',
callback_url: 'https://yoursite.com/webhooks/shadhinpay',
// ... other fields
});Handling Different Events
Payment Completed
The most important event - indicates successful payment:
async function handlePaymentCompleted(data) {
const { payment_id, amount, transaction_id, metadata } = data;
// 1. Verify the payment in your database
const order = await Order.findOne({
where: { payment_id }
});
if (!order) {
console.error('Order not found for payment:', payment_id);
return;
}
// 2. Update order status
await order.update({
status: 'PAID',
transaction_id,
paid_at: new Date()
});
// 3. Trigger fulfillment
await fulfillOrder(order);
// 4. Send confirmation email
await sendConfirmationEmail(order);
}Payment Failed
Handle failures gracefully:
async function handlePaymentFailed(data) {
const { payment_id, customer_phone, metadata } = data;
// Update order status
await Order.update(
{ status: 'PAYMENT_FAILED' },
{ where: { payment_id } }
);
// Notify customer
await sendSMS(customer_phone, 'Your payment could not be processed.');
}Ensuring Idempotency
Webhooks may be delivered multiple times. Always check if you've already processed an event:
async function handleWebhookEvent(event, data) {
const webhookId = `${event}_${data.payment_id}`;
// Check if already processed
const existing = await ProcessedWebhook.findOne({
where: { webhook_id: webhookId }
});
if (existing) {
console.log('Webhook already processed:', webhookId);
return;
}
// Process the webhook
await processWebhook(event, data);
// Mark as processed
await ProcessedWebhook.create({
webhook_id: webhookId,
processed_at: new Date()
});
}Testing Locally
Use ngrok to expose your local server:
# Install ngrok
npm install -g ngrok
# Start your server
node server.js
# In another terminal, start ngrok
ngrok http 3000Use the ngrok URL as your callback_url:
https://abc123.ngrok.io/webhooks/shadhinpayTroubleshooting
Webhooks Not Arriving
- Check URL accessibility - Ensure your endpoint is publicly accessible
- Verify HTTPS - We only send to HTTPS endpoints
- Check firewall - Allow incoming POST requests
- Review logs - Check your webhook dashboard for delivery attempts
Signature Verification Failing
- Use raw body - Don't modify the payload before verifying
- Check secret key - Ensure you're using the correct key
- Encoding - Use UTF-8 encoding for the payload
Duplicate Webhooks
- Implement idempotency - Track processed webhook IDs
- Check timeout - Return 200 within 30 seconds
- Async processing - Process heavy work in background jobs