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
- In the dashboard, open Settings → Webhooks.
- Enter your endpoint URL (must be HTTPS and reachable from the internet).
- Optionally pick which events to receive (leave empty for all events).
- 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
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.):
- Verify the signature (above).
- Re-fetch the transaction with
GET /api/v1/transactions/{uuid} using your API key.
- 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.