Setup Webhooks
Configure webhooks to receive real-time notifications when payment requests are paid, cancelled, or expire.Copy
import { getInstance } from '@handcash/sdk';
const sdk = getInstance({
appId: 'your-app-id',
appSecret: 'your-app-secret'
});
// Register webhook endpoint
const webhook = await sdk.createWebhook({
url: 'https://yoursite.com/webhooks/payments',
events: ['payment.paid', 'payment.cancelled', 'payment.expired'],
secret: 'your-webhook-secret'
});
console.log('Webhook created:', webhook.id);
Webhook Endpoint
Create a webhook endpoint to receive payment notifications:Copy
import express from 'express';
import crypto from 'crypto';
const app = express();
// Middleware to verify webhook signature
const verifyWebhookSignature = (req, res, next) => {
const signature = req.headers['x-handcash-signature'];
const payload = JSON.stringify(req.body);
const secret = process.env.WEBHOOK_SECRET;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (signature !== expectedSignature) {
return res.status(401).send('Invalid signature');
}
next();
};
app.use(express.json());
app.use(verifyWebhookSignature);
app.post('/webhooks/payments', (req, res) => {
const { event, data } = req.body;
switch (event) {
case 'payment.paid':
handlePaymentPaid(data);
break;
case 'payment.cancelled':
handlePaymentCancelled(data);
break;
case 'payment.expired':
handlePaymentExpired(data);
break;
default:
console.log('Unknown event:', event);
}
res.status(200).send('OK');
});
Handle Payment Events
Copy
// Handle successful payment
async function handlePaymentPaid(paymentData) {
console.log('Payment completed:', paymentData.paymentRequestId);
console.log('Amount:', paymentData.amount);
console.log('Currency:', paymentData.currency);
console.log('Transaction ID:', paymentData.transactionId);
console.log('Paid at:', paymentData.paidAt);
// Update your database
await updateOrderStatus(paymentData.paymentRequestId, 'paid');
// Send confirmation email
await sendPaymentConfirmation(paymentData);
// Fulfill order
await fulfillOrder(paymentData.paymentRequestId);
}
// Handle cancelled payment
async function handlePaymentCancelled(paymentData) {
console.log('Payment cancelled:', paymentData.paymentRequestId);
// Update order status
await updateOrderStatus(paymentData.paymentRequestId, 'cancelled');
// Restore inventory
await restoreInventory(paymentData.paymentRequestId);
}
// Handle expired payment
async function handlePaymentExpired(paymentData) {
console.log('Payment expired:', paymentData.paymentRequestId);
// Update order status
await updateOrderStatus(paymentData.paymentRequestId, 'expired');
// Clean up expired order
await cleanupExpiredOrder(paymentData.paymentRequestId);
}
Webhook Payload Structure
Copy
// Payment Paid Event
{
"event": "payment.paid",
"data": {
"paymentRequestId": "pr_123456789",
"amount": 25.00,
"currency": "USD",
"transactionId": "tx_abcdef123456",
"paidAt": "2024-01-15T10:45:00Z",
"payer": {
"handle": "user123",
"displayName": "John Doe"
},
"metadata": {
"orderId": "ORDER-12345",
"customerId": "CUST-67890"
}
},
"timestamp": "2024-01-15T10:45:00Z"
}
// Payment Cancelled Event
{
"event": "payment.cancelled",
"data": {
"paymentRequestId": "pr_123456789",
"cancelledAt": "2024-01-15T10:30:00Z",
"reason": "user_cancelled"
},
"timestamp": "2024-01-15T10:30:00Z"
}
// Payment Expired Event
{
"event": "payment.expired",
"data": {
"paymentRequestId": "pr_123456789",
"expiredAt": "2024-01-15T11:00:00Z"
},
"timestamp": "2024-01-15T11:00:00Z"
}
Webhook Security
Copy
// Verify webhook signature
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// Rate limiting
const rateLimit = require('express-rate-limit');
const webhookLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many webhook requests'
});
app.use('/webhooks', webhookLimiter);
Webhook Management
Copy
// List webhooks
const webhooks = await sdk.getWebhooks();
console.log('Active webhooks:', webhooks);
// Update webhook
const updatedWebhook = await sdk.updateWebhook({
webhookId: 'webhook_123',
url: 'https://yoursite.com/new-webhook-endpoint',
events: ['payment.paid', 'payment.cancelled']
});
// Delete webhook
await sdk.deleteWebhook({
webhookId: 'webhook_123'
});
Testing Webhooks
Copy
// Test webhook endpoint locally
const testWebhook = async () => {
const testPayload = {
event: 'payment.paid',
data: {
paymentRequestId: 'test_pr_123',
amount: 10.00,
currency: 'USD',
transactionId: 'test_tx_456',
paidAt: new Date().toISOString()
},
timestamp: new Date().toISOString()
};
const response = await fetch('http://localhost:3000/webhooks/payments', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Handcash-Signature': generateTestSignature(testPayload)
},
body: JSON.stringify(testPayload)
});
console.log('Test webhook response:', response.status);
};