Skip to main content

What are Webhooks?

Webhooks allow you to receive real-time HTTP notifications when deposit lifecycle events occur, eliminating the need to poll the API.
Webhooks are the recommended way to track deposit status changes. They provide better user experience and reduce API load.

Supported Events

EventDescriptionWhen Triggered
deposit.createdDeposit detectedNGN received in virtual account
deposit.settledDeposit fully settledOn-chain tx confirmed or balance credited
deposit.failedDeposit permanently failedFX failure, on-chain error, or validation error
deposit.flaggedDeposit held for reviewLate deposit, limits exceeded, or risk trigger

Webhook Configuration

Configure webhook endpoints in your Daya Dashboard:
  1. Navigate to Webhooks section
  2. Add webhook URL (must be HTTPS)
  3. Generate webhook secret
  4. Select events to subscribe to
Webhook URLs must use HTTPS in production. HTTP is only allowed in sandbox for local testing.

Webhook Payload

All webhook events follow this structure:
{
  "event": "deposit.settled",
  "event_id": "evt_5k2m8x9q",
  "created_at": "2026-01-14T15:08:15Z",
  "data": {
    "deposit_id": "dep_9x2k5m8p",
    "onramp_id": "onramp_3j5k8n2q",
    "status": "SETTLED",
    "amount_ngn": "15000.00",
    "amount_asset": "9.70",
    "asset": "USDC",
    "chain": "BASE",
    "tx_hash": "0x8f3e2d1c0b9a8e7f6d5c4b3a2e1f0d9c8b7a6e5f4d3c2b1a"
  }
}

Common Fields

event
string
Event typeValues: deposit.created, deposit.settled, deposit.failed, deposit.flagged
event_id
string
Unique identifier for this webhook deliveryUse for: Idempotency (deduplicate multiple deliveries)
created_at
string
When event occurred (ISO 8601 timestamp)
data
object
Event-specific data (varies by event type)

Event-Specific Payloads

Sent when: NGN deposit detected in virtual account
{
  "event": "deposit.created",
  "event_id": "evt_2k5m8x9q",
  "created_at": "2026-01-14T15:06:30Z",
  "data": {
    "deposit_id": "dep_9x2k5m8p",
    "onramp_id": "onramp_3j5k8n2q",
    "payment_reference": "DAYA-3J5K8N2Q",
    "user_email": "[email protected]",
    "amount_ngn": "15000.00",
    "status": "PENDING_FX"
  }
}
Next steps: Monitor for deposit.settled or deposit.failed

Delivery Guarantees

Webhooks may be delivered multiple times. Your endpoint must handle duplicate deliveries using event_id for idempotency.
Events may arrive out of order. Use created_at timestamps to order events client-side.
If your endpoint returns non-2xx status or times out (10 seconds), Daya retries with exponential backoff:
  • Retry 1: After 1 minute
  • Retry 2: After 5 minutes
  • Retry 3: After 30 minutes
  • Retry 4-10: Every hour (up to 24 hours total)
After 24 hours, delivery is marked as failed.
Your endpoint must respond within 10 seconds. Longer responses will timeout and trigger retries.

Webhook Verification

All webhooks include an HMAC-SHA256 signature in the X-Daya-Signature header. Always verify signatures to prevent spoofing. See Webhook Verification for implementation details.

Implementing a Webhook Endpoint

Required Response

Your endpoint must:
  1. Verify signature (see Verification)
  2. Return 2xx status to acknowledge receipt
  3. Process quickly (< 10 seconds) or queue for async processing

Example Implementation

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

app.post('/webhooks/daya', (req, res) => {
  const signature = req.headers['x-daya-signature'];
  const payload = JSON.stringify(req.body);
  
  // 1. Verify signature
  if (!verifySignature(payload, signature, process.env.DAYA_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  
  // 2. Check idempotency
  const eventId = req.body.event_id;
  if (isProcessed(eventId)) {
    return res.status(200).send('Already processed');
  }
  
  // 3. Queue for processing (return quickly)
  queue.add('process-webhook', req.body);
  markAsProcessed(eventId);
  
  // 4. Return 200
  res.status(200).send('OK');
});

function verifySignature(payload, signature, secret) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Best Practices

1

Verify signatures

Always verify X-Daya-Signature to prevent spoofing attacks.
2

Handle idempotency

Use event_id to deduplicate. Store processed event IDs in your database.
CREATE TABLE processed_webhook_events (
  event_id VARCHAR(255) PRIMARY KEY,
  processed_at TIMESTAMP
);
3

Return 200 quickly

Acknowledge receipt immediately (< 1 second). Queue heavy processing asynchronously.
4

Handle out-of-order delivery

Use created_at timestamps to order events correctly.
5

Monitor webhook health

Track failed deliveries in your dashboard and set up alerts.
6

Test with sandbox

Use sandbox environment to test webhook handling before production.

Testing Webhooks

Sandbox Testing

In sandbox, trigger test webhooks:
  1. Create test onramp
  2. Simulate deposit via dashboard or test API
  3. Receive webhook at your endpoint

Local Testing

For local development, use tools like ngrok:
# Start ngrok
ngrok http 3000

# Configure webhook URL in dashboard
https://abc123.ngrok.io/webhooks/daya
Use ngrok’s web interface (http://localhost:4040) to inspect webhook payloads during development.

Troubleshooting

Possible causes:
  • Firewall blocking Daya’s IPs
  • Endpoint returning non-2xx status
  • SSL certificate issues
Fix: Check endpoint logs, ensure HTTPS, verify firewall rules
Expected behavior: At-least-once delivery means duplicates are possibleFix: Implement idempotency using event_id
Cause: Endpoint taking > 10 seconds to respondFix: Return 200 immediately, queue processing asynchronously
Cause: Wrong secret or payload manipulationFix: Verify you’re using correct webhook secret from dashboard

Next Steps