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-Signaturebefore 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_referenceas 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"
}
| Field | Description |
|---|---|
event | Event type: payment.success or payment.failed. |
dgs_reference | Your transaction ID — use this for idempotency checks. |
merchant_fee | Fee charged to your account for this transaction. |
net_amount | Amount credited to your merchant wallet (amount − merchant_fee). |
environment | "test" or "production". |