Skip to main content

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

EventDescription
payment.intent.createdPayment intent created
payment.intent.processingPayment is processing
payment.intent.succeededPayment successful
payment.intent.failedPayment failed
payment.intent.expiredPayment expired
refund.createdRefund initiated
refund.processingRefund processing
refund.completedRefund completed
refund.failedRefund failed
payout.createdPayout created
payout.completedPayout completed
payout.failedPayout failed
settlement.initiatedSettlement started
settlement.completedSettlement 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

  1. Respond quickly - Return 200 within 5 seconds
  2. Process asynchronously - Queue events for background processing
  3. Handle duplicates - Events may be sent multiple times
  4. Use HTTPS - Always use secure endpoints
  5. Verify signatures - Never trust unverified payloads
  6. Log everything - Keep records for debugging