Skip to content

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)

javascript
//  Inefficient - polls every 2 seconds
setInterval(async () => {
  const payment = await fetchPayment(paymentId)

  if (payment.status === 'completed') {
    fulfillOrder(payment)
  }
}, 2000)

With Webhooks

javascript
//  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:

javascript
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:

bash
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:

javascript
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

javascript
// 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

javascript
// Refund completed
{
  "type": "refund.completed",
  "data": {
    "refund_id": "refund-xyz789",
    "payment_id": "payment-abc123",
    "amount": 2500,
    "reason": "Customer requested cancellation"
  }
}

Payout Events

javascript
// Payout sent to bank
{
  "type": "payout.paid",
  "data": {
    "payout_id": "payout-abc123",
    "amount": 92000,
    "arrival_date": "2026-03-21"
  }
}

Handling Events

Complete Example

javascript
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

javascript
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

javascript
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

javascript
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

bash
# 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/dberi

Send Test Webhooks

bash
# 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

javascript
// 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:

javascript
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:

javascript
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 later

Ensure your endpoint can handle retries gracefully.

4. Verify Signatures

Always verify signatures:

javascript
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

javascript
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: 145ms

Failed 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/5

Security

Use HTTPS

Always use HTTPS endpoints in production:

 https://yourstore.com/webhooks/dberi
 http://yourstore.com/webhooks/dberi

Verify Signatures

Never skip signature verification:

javascript
//  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:

bash
POST /v1/webhooks/:id/rotate-secret

Troubleshooting

Webhook Not Received

  1. Check webhook is registered in dashboard
  2. Verify URL is publicly accessible
  3. Check firewall/security group settings
  4. Test with ngrok locally

Signature Verification Failed

  1. Ensure using raw request body (not parsed)
  2. Verify correct webhook secret
  3. Check header name is exact: x-dberi-signature
  4. Test with known-good signature

Duplicate Webhooks

Handle with idempotency (store event IDs)

Next Steps

Built for the Bahamas, powered by innovation