Webhooks
Betavend sends real-time HTTP POST notifications to your endpoint when events occur — transaction status changes, wallet credits, and more.
Overview
Instead of polling the API to check transaction status, configure a webhook URL on your server and Betavend will push events to you as they happen. Webhook delivery is guaranteed with automatic retries.
Configure your webhook URL at business.betavend.com/webhooks. You can register multiple URLs for different event types.
Event Types
| Event | When it fires |
|---|---|
transaction.completed | A service purchase was successfully delivered |
transaction.failed | A transaction failed and wallet was auto-reversed |
transaction.processing | A transaction is being processed by the provider |
wallet.credited | Wallet was topped up via bank transfer |
wallet.debited | Wallet was debited for a purchase |
wallet.reversed | A failed transaction was reversed back to wallet |
Payload Format
All webhook events share the same envelope structure:
POST https://your-server.com/webhook
Content-Type: application/json
X-Betavend-Signature: sha256=abc123...
X-Betavend-Event: transaction.completed
{
"event": "transaction.completed",
"id": "evt_abc123",
"created_at": "2026-05-02T14:00:05Z",
"data": {
"reference": "BV-20260502-001",
"service_type": "data",
"network": "mtn",
"recipient": "08012345678",
"amount": 550,
"status": "delivered",
"delivered_at": "2026-05-02T14:00:05Z"
}
}
Signature Verification
Every webhook includes an X-Betavend-Signature header. Always verify this before processing:
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
// In your Express handler:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-betavend-signature'];
if (!verifyWebhook(req.body, sig, process.env.BETAVEND_WEBHOOK_SECRET)) {
return res.status(400).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process event...
res.json({ received: true });
});
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_BETAVEND_SIGNATURE'] ?? '';
$secret = getenv('BETAVEND_WEBHOOK_SECRET');
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(400);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// Process $event...
Retry Policy
If your endpoint returns anything other than a 2xx status, Betavend retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 8 hours |
After 5 failed attempts the event is marked as dead. You can manually replay failed events from business.betavend.com/webhooks.
Best Practices
- Respond quickly — return
200 OKimmediately. Do heavy processing asynchronously. - Be idempotent — webhooks may be delivered more than once. Use
event.idto deduplicate. - Always verify the signature — never process unsigned payloads.
- Check event type — always read the
eventfield before processing. New event types may be added. - Use HTTPS only — Betavend will not deliver webhooks to HTTP endpoints.