Skip to main content

Setting up webhooks

Register an HTTPS endpoint to receive events:
curl -X POST https://api.paypulse.cv/api/v1/webhooks \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: pp_test_xxxx" \
  -H "Authorization: Bearer eyJhbGci..." \
  -d '{
    "url": "https://yourapp.com/webhooks/paypulse",
    "events": ["invoice.paid", "subscription.cancelled"]
  }'

Event payload

Every webhook delivery includes:
HeaderDescription
X-SignatureHMAC-SHA256 signature of the body
X-Webhook-IdUnique delivery ID
X-TimestampUnix timestamp of delivery

Body example

{
  "event": "invoice.paid",
  "data": {
    "id": "inv_xxxx",
    "amount": 25000,
    "currency": "NGN",
    "customer_email": "ade@techcorp.ng",
    "status": "PAID",
    "paid_at": "2026-01-15T10:30:05Z"
  },
  "created_at": "2026-01-15T10:30:05Z"
}

Verifying signatures

Always verify the X-Signature header to ensure the webhook came from PayPulse:
import hmac
import hashlib

def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

Retry policy

Failed deliveries (non-2xx response or timeout) are retried up to 5 times with exponential backoff:
AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
After 5 failed attempts, the delivery is marked as FAILED and appears in your webhook delivery logs.

Testing webhooks

Use the test endpoint to send a mock event:
curl -X POST https://api.paypulse.cv/api/v1/webhooks/test \
  -H "X-Api-Key: pp_test_xxxx" \
  -H "Authorization: Bearer eyJhbGci..." \
  -d '{
    "webhook_id": "your-webhook-id",
    "event": "invoice.paid"
  }'