Webhooks
Receive real-time notifications about payment events.
Setting Up Webhooks
1. Create a Webhook Endpoint
Create an endpoint on your server to receive webhook events:
// Express.js example
app.post('/webhooks/zenpays', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body
const signature = req.headers['x-webhook-signature']
// Verify and process the webhook
try {
const event = verifyWebhook(payload, signature)
handleWebhookEvent(event)
res.status(200).send('OK')
}
catch (error) {
console.error('Webhook error:', error)
res.status(400).send('Invalid webhook')
}
})
2. Register the Webhook
const webhook = await zenpays.merchants.createWebhook({
url: 'https://your-site.com/webhooks/zenpays',
events: [
'payment.intent.succeeded',
'payment.intent.failed',
'refund.completed',
'payout.completed',
],
})
// Store the webhook secret for verification
console.log('Webhook secret:', webhook.secret)
Webhook Events
| Event | Description |
|---|---|
payment.intent.created | Payment intent created |
payment.intent.processing | Payment is processing |
payment.intent.succeeded | Payment successful |
payment.intent.failed | Payment failed |
payment.intent.expired | Payment expired |
refund.created | Refund initiated |
refund.processing | Refund processing |
refund.completed | Refund completed |
refund.failed | Refund failed |
payout.created | Payout created |
payout.completed | Payout completed |
payout.failed | Payout failed |
settlement.initiated | Settlement started |
settlement.completed | Settlement completed |
Webhook Payload
interface WebhookEvent {
id: string
type: string
created: string
data: {
object: PaymentIntent | Refund | Payout | Settlement
}
}
Example payload:
{
"id": "evt_xxx",
"type": "payment.intent.succeeded",
"created": "2024-01-15T10:30:00Z",
"data": {
"object": {
"id": "pi_xxx",
"amount": 1000,
"currency": "USD",
"status": "succeeded",
"customerEmail": "john@example.com"
}
}
}
Verifying Webhooks
Always verify webhook signatures to ensure authenticity:
import { Buffer } from 'node:buffer'
import crypto from 'node:crypto'
function verifyWebhook(
payload: Buffer,
signature: string,
secret: string
): WebhookEvent {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
if (signature !== `sha256=${expectedSignature}`) {
throw new Error('Invalid webhook signature')
}
return JSON.parse(payload.toString())
}
Handling Events
function handleWebhookEvent(event: WebhookEvent) {
switch (event.type) {
case 'payment.intent.succeeded':
handlePaymentSuccess(event.data.object)
break
case 'payment.intent.failed':
handlePaymentFailure(event.data.object)
break
case 'refund.completed':
handleRefundComplete(event.data.object)
break
default:
console.log('Unhandled event:', event.type)
}
}
function handlePaymentSuccess(payment: PaymentIntent) {
// Update order status
// Send confirmation email
// Fulfill the order
}
function handlePaymentFailure(payment: PaymentIntent) {
// Notify customer
// Cancel the order
}
function handleRefundComplete(refund: Refund) {
// Update order status
// Notify customer
}
Testing Webhooks
Test a Webhook Endpoint
const result = await zenpays.merchants.testWebhook(
'wh_xxx',
'payment.intent.succeeded'
)
if (result.success) {
console.log('Webhook delivered successfully')
}
else {
console.error('Webhook failed:', result.error)
}
View Delivery Logs
const logs = await zenpays.merchants.getWebhookLogs('wh_xxx', {
from: '2024-01-01',
limit: 50,
})
logs.forEach((log) => {
console.log(`${log.eventType}: ${log.statusCode} at ${log.deliveredAt}`)
})
Best Practices
- Respond quickly - Return 200 within 5 seconds
- Process asynchronously - Queue events for background processing
- Handle duplicates - Events may be sent multiple times
- Use HTTPS - Always use secure endpoints
- Verify signatures - Never trust unverified payloads
- Log everything - Keep records for debugging