Webhooks
Receive real-time notifications for payment and payout events
Snippe sends webhooks to your specified URL when payment or payout status changes. Use webhooks to update your system in real-time.
Always provide a webhook_url when creating payments or payouts to receive
status notifications.
Webhook Headers
All webhooks include the following headers:
| Header | Description |
|---|---|
Content-Type | application/json |
User-Agent | Snipe-Webhook/1.0 |
X-Webhook-Event | Event type (e.g., payment.completed) |
X-Webhook-Timestamp | Unix timestamp when webhook was sent |
X-Webhook-Signature | HMAC-SHA256 signature for verification |
Event Types
| Event | Description | | ----- | ----------- | | payment.completed |
Payment successfully completed | | payment.failed | Payment failed or was
declined |
| Event | Description | | ----- | ----------- | | payout.completed |
Payout successfully sent | | payout.failed | Payout failed |
Payment Events
payment.completed
Sent when a payment is successfully completed.
Trigger: Customer completes payment via mobile money, card, or QR code.
Action: Mark the order as paid, deliver the product/service.
{
"id": "evt_427edf89c5c8c02a2301254e",
"type": "payment.completed",
"api_version": "2026-01-25",
"created_at": "2026-01-25T01:05:17.834276191Z",
"data": {
"reference": "9015c155-9e29-4e8e-8fe6-d5d81553c8e6",
"external_reference": "S20388385575",
"status": "completed",
"amount": {
"value": 500,
"currency": "TZS"
},
"settlement": {
"gross": { "value": 500, "currency": "TZS" },
"fees": { "value": 9, "currency": "TZS" },
"net": { "value": 491, "currency": "TZS" }
},
"channel": {
"type": "mobile_money",
"provider": "airtel"
},
"customer": {
"phone": "+255781000000",
"name": "Customer Name",
"email": "customer@email.com"
},
"metadata": {
"order_id": "ORD-12345",
"product": "Premium Plan"
},
"completed_at": "2026-01-25T01:05:16.8303Z"
}
}payment.failed
Sent when a payment fails.
Trigger: Payment was declined, insufficient funds, or other failure.
Action: Notify the customer, offer retry option.
{
"id": "evt_a1b2c3d4e5f6g7h8i9j0k1l2",
"type": "payment.failed",
"api_version": "2026-01-25",
"created_at": "2026-01-25T01:05:17.834276191Z",
"data": {
"reference": "9015c155-9e29-4e8e-8fe6-d5d81553c8e6",
"external_reference": "S20388385575",
"status": "failed",
"amount": {
"value": 500,
"currency": "TZS"
},
"settlement": {
"gross": { "value": 500, "currency": "TZS" },
"fees": { "value": 9, "currency": "TZS" },
"net": { "value": 491, "currency": "TZS" }
},
"channel": {
"type": "mobile_money",
"provider": "airtel"
},
"customer": {
"phone": "+255781000000",
"name": "Customer Name",
"email": "customer@email.com"
},
"metadata": {
"order_id": "ORD-12345"
},
"failure_reason": "Something went wrong",
"completed_at": "2026-01-25T01:05:16.8303Z"
}
}Payout Events
payout.completed
Sent when a payout is successfully completed.
Trigger: Funds have been successfully sent to the recipient.
Action: Update your records, notify sender of successful transfer.
{
"id": "evt_a1b2c3d4e5f6g7h8i9j0k1l2",
"type": "payout.completed",
"api_version": "2026-01-25",
"created_at": "2026-01-25T10:30:00Z",
"data": {
"reference": "PAY-ABC123XYZ",
"status": "completed",
"amount": {
"value": 50000,
"currency": "TZS"
},
"settlement": {
"gross": { "value": 50000, "currency": "TZS" },
"fees": { "value": 500, "currency": "TZS" },
"net": { "value": 50500, "currency": "TZS" }
},
"channel": {
"type": "mobile_money",
"provider": "MPESA"
},
"customer": {
"phone": "255712345678",
"name": "Customer Name"
},
"metadata": {
"order_id": "12345"
},
"completed_at": "2026-01-25T10:30:00Z"
}
}payout.failed
Sent when a payout fails.
Trigger: Payout could not be completed (invalid recipient, network error, etc.).
Action: Funds are returned to balance, notify sender of failure.
{
"id": "evt_a1b2c3d4e5f6g7h8i9j0k1l2",
"type": "payout.failed",
"api_version": "2026-01-25",
"created_at": "2026-01-25T10:30:00Z",
"data": {
"reference": "PAY-ABC123XYZ",
"status": "failed",
"amount": {
"value": 50000,
"currency": "TZS"
},
"settlement": {
"gross": { "value": 50000, "currency": "TZS" },
"fees": { "value": 500, "currency": "TZS" },
"net": { "value": 50500, "currency": "TZS" }
},
"channel": {
"type": "mobile_money",
"provider": "MPESA"
},
"customer": {
"phone": "255712345678",
"name": "Customer Name"
},
"metadata": {
"order_id": "12345"
},
"failure_reason": "Recipient phone number is invalid"
}
}Webhook Signature Verification
Always verify webhook signatures in production to ensure requests are from Snippe.
Validate the X-Webhook-Signature header using HMAC-SHA256:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verifyWebhookSignature(payload, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expected))
}Best Practices
Respond Quickly
Return a 2xx status code within 30 seconds. Process webhooks asynchronously.
Handle Duplicates
Use the event id to deduplicate. You may receive the same event multiple times.
Verify Signatures
Always validate webhook signatures in production environments.
Implement Retries
Snippe retries failed webhooks with exponential backoff. Ensure your endpoint is idempotent.
If your endpoint consistently fails, webhooks may be disabled. Monitor your webhook endpoint health.