Fortpesa POSTs a signed event to your endpoint every time one of your transactions reaches a terminal state (success, failed, cancelled, expired, reversed). Webhooks are the recommended way to fulfil orders — no polling required.

Setting up

  1. In the dashboard, open Settings → Webhooks.
  2. Enter your endpoint URL (must be HTTPS and reachable from the internet).
  3. Optionally pick which events to receive (leave empty for all events).
  4. Save — the signing secret (whsec_…) is shown once. Copy it immediately.
Store the secret in an environment variable on your server. If you lose it, delete the webhook and create a new one (each endpoint gets its own secret). You can pause a webhook with the Toggle button (for example during maintenance) and delete it permanently at any time.

Events

Payload

Request headers

Verifying signatures

Every delivery is signed with HMAC-SHA256 over "{timestamp}.{raw_body}" using your webhook secret:
Verify against the raw request body bytes, before any JSON parsing — re-serializing parsed JSON changes byte order and will break the comparison. Also check the timestamp is recent (within 5 minutes) to reject replays, and compare in constant time.

Trust but verify

The payload is a notification, not proof. Before fulfilling an order (or refunding, granting access, etc.):
  1. Verify the signature (above).
  2. Re-fetch the transaction with GET /api/v1/transactions/{uuid} using your API key.
  3. Act only on what that read says.
This makes your integration safe even if a delivery is stale or your endpoint logs are compromised.

Responses and retries

  • Respond with any 2xx status as soon as you have verified the signature — do your heavy work asynchronously. We time out after 10 seconds.
  • Non-2xx responses (or timeouts) are retried up to 5 attempts total, with delays of roughly 10s, 30s, 60s, and 120s.
  • After 20 consecutive failures the webhook is automatically disabled. Re-enable it with Toggle in Settings → Webhooks once your endpoint is healthy again.

Testing locally

During development, point your webhook at a tunnel URL (for example an ngrok or Cloudflare tunnel to localhost), then trigger a payment. Verify signatures exactly the same way you would in production.