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:

HeaderDescription
Content-Typeapplication/json
User-AgentSnipe-Webhook/1.0
X-Webhook-EventEvent type (e.g., payment.completed)
X-Webhook-TimestampUnix timestamp when webhook was sent
X-Webhook-SignatureHMAC-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.

On this page