Webhook Integration
Receive real-time notifications when payments and other events occur in your Dberi account.
Coming Soon
Full webhook functionality is currently in development. This guide describes the planned webhook system.
Why Use Webhooks?
Instead of constantly polling the API to check payment status, webhooks push notifications to your server automatically when events occur.
Without Webhooks (Polling)
// Inefficient - polls every 2 seconds
setInterval(async () => {
const payment = await fetchPayment(paymentId)
if (payment.status === 'completed') {
fulfillOrder(payment)
}
}, 2000)With Webhooks
// Efficient - notified instantly
app.post('/webhooks/dberi', (req, res) => {
const event = req.body
if (event.type === 'payment.completed') {
fulfillOrder(event.data)
}
res.status(200).send('OK')
})Setup
1. Create Webhook Endpoint
Create an endpoint on your server to receive webhooks:
const express = require('express')
const app = express()
app.post('/webhooks/dberi',
express.raw({type: 'application/json'}),
(req, res) => {
const event = JSON.parse(req.body)
// Process event
handleWebhook(event)
// Respond immediately
res.status(200).send('OK')
}
)
app.listen(3000)2. Register Webhook URL
Register your webhook URL in the dashboard or API:
curl -X POST https://api.dberi.com/v1/webhooks \
-H "Authorization: Bearer sk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourstore.com/webhooks/dberi",
"events": [
"payment.completed",
"payment.failed",
"refund.completed"
],
"description": "Production webhook"
}'3. Verify Signatures
Always verify webhook signatures to ensure requests come from Dberi:
const crypto = require('crypto')
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
}
app.post('/webhooks/dberi', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-dberi-signature']
if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
// Process webhook...
})Event Types
Payment Events
// Payment successfully completed
{
"type": "payment.completed",
"data": {
"payment_id": "payment-abc123",
"merchant_id": "merchant-your-id",
"amount": 2500,
"status": "completed",
"customer_id": "cust-xyz789",
"metadata": { "order_id": "1234" }
}
}
// Payment failed
{
"type": "payment.failed",
"data": {
"payment_id": "payment-abc123",
"error_code": "INSUFFICIENT_BALANCE",
"error_message": "Customer has insufficient funds"
}
}
// Payment expired (24 hours)
{
"type": "payment.expired",
"data": {
"payment_id": "payment-abc123"
}
}Refund Events
// Refund completed
{
"type": "refund.completed",
"data": {
"refund_id": "refund-xyz789",
"payment_id": "payment-abc123",
"amount": 2500,
"reason": "Customer requested cancellation"
}
}Payout Events
// Payout sent to bank
{
"type": "payout.paid",
"data": {
"payout_id": "payout-abc123",
"amount": 92000,
"arrival_date": "2026-03-21"
}
}Handling Events
Complete Example
const express = require('express')
const crypto = require('crypto')
const app = express()
app.post('/webhooks/dberi',
express.raw({type: 'application/json'}),
async (req, res) => {
// 1. Verify signature
const signature = req.headers['x-dberi-signature']
const isValid = verifySignature(
req.body,
signature,
process.env.WEBHOOK_SECRET
)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
// 2. Parse event
const event = JSON.parse(req.body)
// 3. Respond immediately
res.status(200).send('OK')
// 4. Process asynchronously
processWebhookAsync(event)
}
)
async function processWebhookAsync(event) {
try {
// Check if already processed
const exists = await db.webhookEvents.findOne({ id: event.id })
if (exists) {
console.log('Webhook already processed')
return
}
// Handle event type
switch (event.type) {
case 'payment.completed':
await handlePaymentCompleted(event.data)
break
case 'payment.failed':
await handlePaymentFailed(event.data)
break
case 'refund.completed':
await handleRefundCompleted(event.data)
break
default:
console.log(`Unhandled event: ${event.type}`)
}
// Mark as processed
await db.webhookEvents.create({
id: event.id,
type: event.type,
processed: true,
processed_at: new Date()
})
} catch (error) {
console.error('Webhook processing error:', error)
// Log error for retry
await logWebhookError(event, error)
}
}
app.listen(3000)Handle Payment Completed
async function handlePaymentCompleted(data) {
const { payment_id, metadata } = data
const { order_id } = metadata
console.log(`Payment completed: ${payment_id} for order ${order_id}`)
// 1. Update order in database
await db.orders.update(order_id, {
status: 'paid',
payment_id,
paid_at: new Date()
})
// 2. Send confirmation email
const order = await db.orders.findOne(order_id)
await sendEmail({
to: order.customer_email,
subject: `Order #${order_id} Confirmed`,
template: 'order_confirmation',
data: {
order_id,
items: order.items,
total: data.amount
}
})
// 3. Create shipment
if (order.requires_shipping) {
await shippingService.createShipment(order)
}
// 4. Notify internal systems
await notifyWarehouse(order_id)
await updateInventory(order.items)
await syncToAccountingSystem(payment_id)
// 5. Analytics
await trackConversion(order_id, data.amount)
console.log(`Order ${order_id} fulfilled`)
}Handle Payment Failed
async function handlePaymentFailed(data) {
const { payment_id, error_code, metadata } = data
const { order_id } = metadata
console.log(`Payment failed: ${payment_id} - ${error_code}`)
// 1. Update order status
await db.orders.update(order_id, {
status: 'payment_failed',
error_code,
failed_at: new Date()
})
// 2. Release inventory hold
const order = await db.orders.findOne(order_id)
await releaseInventory(order.items)
// 3. Notify customer
await sendEmail({
to: order.customer_email,
subject: 'Payment Failed',
template: 'payment_failed',
data: {
order_id,
error_code,
retry_url: `https://yourstore.com/retry/${order_id}`
}
})
// 4. Analytics
await trackFailedPayment(order_id, error_code)
}Handle Refund Completed
async function handleRefundCompleted(data) {
const { refund_id, payment_id, amount, reason } = data
console.log(`Refund completed: ${refund_id}`)
// Find order
const order = await db.orders.findOne({ payment_id })
// Update order status
await db.orders.update(order.id, {
status: 'refunded',
refund_id,
refund_amount: amount,
refund_reason: reason,
refunded_at: new Date()
})
// Notify customer
await sendEmail({
to: order.customer_email,
subject: 'Refund Processed',
template: 'refund_confirmation',
data: {
order_id: order.id,
amount,
reason
}
})
// Update accounting
await syncRefundToAccountingSystem(refund_id)
}Testing Webhooks
Local Testing with ngrok
# Start your server
node server.js
# In another terminal, start ngrok
ngrok http 3000
# Use ngrok URL as webhook endpoint
# https://abc123.ngrok-free.app/webhooks/dberiSend Test Webhooks
# Send test payment.completed webhook
curl -X POST https://api.dberi.com/v1/webhooks/test \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"webhook_id": "webhook-abc123",
"event_type": "payment.completed"
}'Test Locally
// test-webhook.js
const fetch = require('node-fetch')
const crypto = require('crypto')
async function testWebhook() {
const payload = {
id: 'evt_test123',
type: 'payment.completed',
created: new Date().toISOString(),
data: {
payment_id: 'payment-test123',
merchant_id: 'merchant-test',
amount: 2500,
status: 'completed',
metadata: {
order_id: '1234'
}
}
}
const secret = process.env.WEBHOOK_SECRET
const signature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex')
const response = await fetch('https://api.dberi.com/webhooks/dberi', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Dberi-Signature': signature
},
body: JSON.stringify(payload)
})
console.log('Status:', response.status)
console.log('Response:', await response.text())
}
testWebhook()Best Practices
1. Respond Quickly
Always respond with 200 OK immediately:
app.post('/webhooks/dberi', (req, res) => {
// Respond immediately
res.status(200).send('OK')
// Process asynchronously
processWebhookAsync(req.body)
})2. Handle Idempotency
Store event IDs to prevent duplicate processing:
async function processWebhook(event) {
// Check if already processed
const exists = await db.webhookEvents.findOne({ id: event.id })
if (exists) {
return // Already processed
}
// Process event
await handleEvent(event)
// Mark as processed
await db.webhookEvents.create({ id: event.id })
}3. Retry Failed Processing
If processing fails, Dberi will retry:
Attempt 1: Immediate
Attempt 2: 5 minutes later
Attempt 3: 30 minutes later
Attempt 4: 2 hours later
Attempt 5: 6 hours laterEnsure your endpoint can handle retries gracefully.
4. Verify Signatures
Always verify signatures:
const signature = req.headers['x-dberi-signature']
const timestamp = req.headers['x-dberi-timestamp']
// Prevent replay attacks
if (Date.now() - parseInt(timestamp) > 300000) {
return res.status(400).send('Webhook too old')
}
// Verify signature
if (!verifySignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature')
}5. Log Everything
async function processWebhook(event) {
console.log('Webhook received:', {
id: event.id,
type: event.type,
timestamp: new Date()
})
try {
await handleEvent(event)
console.log('Webhook processed successfully')
} catch (error) {
console.error('Webhook processing failed:', error)
await logError(event, error)
}
}Monitoring
View Webhook Logs
Check webhook delivery status in your dashboard:
Event ID: evt_abc123
Type: payment.completed
Status: 200 OK
Delivered: 2026-03-19 10:05:31
Duration: 145msFailed Webhooks
View failed webhooks:
Event ID: evt_def456
Type: payment.completed
Status: 500 Internal Server Error
Last Attempt: 2026-03-19 10:05:31
Next Retry: 2026-03-19 10:10:31
Attempts: 2/5Security
Use HTTPS
Always use HTTPS endpoints in production:
https://yourstore.com/webhooks/dberi
http://yourstore.com/webhooks/dberiVerify Signatures
Never skip signature verification:
// Good
if (!verifySignature(payload, signature, secret)) {
return res.status(401).send('Invalid signature')
}
// Bad
// if (process.env.NODE_ENV !== 'production') {
// Skip verification in development - NEVER DO THIS
// }Rotate Secrets
Rotate webhook secrets every 90 days:
POST /v1/webhooks/:id/rotate-secretTroubleshooting
Webhook Not Received
- Check webhook is registered in dashboard
- Verify URL is publicly accessible
- Check firewall/security group settings
- Test with ngrok locally
Signature Verification Failed
- Ensure using raw request body (not parsed)
- Verify correct webhook secret
- Check header name is exact:
x-dberi-signature - Test with known-good signature
Duplicate Webhooks
Handle with idempotency (store event IDs)