DGS-Pay API v2
Dashboard
Notifications

Webhooks

Webhooks are HTTP POST requests that DGS-Pay sends to your server when a payment event occurs. They are the reliable mechanism for confirming payment completion — redirect URLs can be blocked or closed by the user, but webhooks are server-to-server.

Configure your webhook URL in the merchant portal under Settings → API Settings. You can also override the URL per-transaction using the callback_url field.

Always Use Webhooks to Confirm Payment Do not mark an order as paid based only on a redirect URL parameter or an API response with status: "pending". Wait for the payment.success webhook event.

Signature Verification

Every webhook request includes an X-DGS-Signature header. This is an HMAC-SHA256 hash of the raw request body, signed with your Webhook Secret (find it in Settings → API Settings). Always verify this signature before processing any webhook payload — it proves the request came from DGS-Pay and was not tampered with.

PHP
<?php
$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_DGS_SIGNATURE'];
$expected  = hash_hmac('sha256', $payload, DGS_WEBHOOK_SECRET);

if (!hash_equals($expected, $signature)) {
  http_response_code(401);
  exit('Unauthorized');
}

$event = json_decode($payload, true);
// Handle $event['event'] here

http_response_code(200);
echo json_encode(['status' => 'received']);
?>
Node.js / Express
const crypto = require('crypto');

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig      = req.headers['x-dgs-signature'];
  const expected = crypto
    .createHmac('sha256', process.env.DGS_WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return res.status(401).send('Unauthorized');
  }

  const event = JSON.parse(req.body);
  // Handle event.event here

  res.json({ status: 'received' });
});
Python / Flask
import hmac, hashlib, os
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
  sig      = request.headers.get('X-DGS-Signature', '')
  secret   = os.environ['DGS_WEBHOOK_SECRET'].encode()
  expected = hmac.new(secret, request.data, hashlib.sha256).hexdigest()

  if not hmac.compare_digest(sig, expected):
    return 'Unauthorized', 401

  event = request.get_json()
  # Handle event['event'] here

  return jsonify({ 'status': 'received' })
Webhook Rules
  • Always verify X-DGS-Signature before processing the payload.
  • Respond with HTTP 200 immediately — delayed or failed responses trigger automatic retries.
  • Webhook delivery is at-least-once — handle duplicates using dgs_reference as an idempotency key.

Payment Event Payloads

JSON
{
  "event": "payment.success",
  "dgs_reference": "dgs_123456789",
  "flw_charge_id": "flw_987654321",
  "amount": 5000,
  "currency": "RWF",
  "merchant_fee": 150,
  "net_amount": 4850,
  "status": "success",
  "timestamp": "2026-04-02T10:30:00Z",
  "environment": "production"
}
JSON
{
  "event": "payment.failed",
  "dgs_reference": "dgs_123456789",
  "flw_charge_id": "flw_987654321",
  "amount": 5000,
  "currency": "RWF",
  "merchant_fee": 0,
  "net_amount": 0,
  "status": "failed",
  "timestamp": "2026-04-02T10:35:00Z",
  "environment": "production"
}
FieldDescription
eventEvent type: payment.success or payment.failed.
dgs_referenceYour transaction ID — use this for idempotency checks.
merchant_feeFee charged to your account for this transaction.
net_amountAmount credited to your merchant wallet (amount − merchant_fee).
environment"test" or "production".

Need Help?

Our technical team is ready to assist with integration questions, testing, and go-live checks.

Contact Support