Skip to main content
A webhook is a POST we send to your server when something happens — money arrived, a payment settled, a refund went through. You need them. There is no polling endpoint that tells you a credit landed, so without a webhook URL you will not know your buyer has paid.

Events we send

Payload for each is in the API Reference.
LRS payments get LRS_VERIFICATION_NEEDED, not VERIFICATION_NEEDED. If you collect education fees and you only handle VERIFICATION_NEEDED, your handler will never fire.
A verified payment sends nothing. VERIFIED means nothing is outstanding, so there is no event — do not wait on one to confirm a payment passed.

Set it up

1. Build the endpoint. A public HTTPS URL that accepts POST with application/json and returns 200 OK. 2. Register it. In your EximPe Dashboard, go to Developer and enter the URL. If you cannot use the dashboard, send the URL and your server IP to EximPe Support instead. 3. Whitelist our IPs. The ranges are in the same Developer section. Configure your firewall to accept webhooks only from those addresses. 4. Test it. Make a sandbox payment, watch it arrive, confirm you return 200.

Driving every event in sandbox

You do not have to wait for real money, or a real settlement date, to exercise your handlers. Sandbox lets you fire each event on demand. Set Refund Status is the useful one for coverage: force a single refund through REFUNDED, FAILED, PROCESSING, REVERSED, ON_HOLD and BANK_REJECTED_REFUND in turn, and one refund exercises every branch of your handler. INITIATED fires nothing — it is the starting state, not a transition.
Both need the Simulator gateway. They only work on transactions created through it, and return 400 in production. If your sandbox account is not on the Simulator gateway, ask your EximPe integration manager to assign it.
Mark as Settled applies a simulated fee schedule, not your contracted rates. Use it to check that your reconciliation arithmetic holds, not to predict what a production settlement will pay you.

Check the message is really from us

Every webhook carries an HMAC-SHA256 signature. Verify it before you act on anything — otherwise anyone who finds your URL can tell you a payment succeeded. There are two headers, and you get both on every delivery:
Prefer V2. Its timestamp is signed along with the body, so a captured delivery can’t be replayed later with a fresh one. Split the header on ,, take t and v2, rebuild the hash over t + "." + raw_body, and reject anything whose t is too old. V1 stays alongside it so nobody has to migrate on our schedule; the walk-through below covers V1.
1

Take the signature from the header

Read X-Webhook-Signature off the request.
2

Get your Encryption Key

It is in the Developer section of the dashboard.
Get your Encryption Key from Developer Section
3

Rebuild the signature

HMAC-SHA256, using your Encryption Key as the secret and the raw request body as the message. Output as a hex string.
4

Compare

They must match exactly. Use a timing-safe comparison.
Use the raw body, not a re-serialised object. If you parse the JSON and stringify it again, your bytes will differ from ours and the signature will never match. If you have no choice, serialise with sorted keys and no spaces — json.dumps(data, sort_keys=True, separators=(",", ":")).

Code

If your endpoint is down

We retry. First attempt is immediate, then at 1 minute, 5 minutes, 15 minutes, and 1 hour. After 5 failed attempts we stop. Anything other than 200 OK counts as a failure, so return 200 as soon as you have stored the event, and do your processing afterwards.
Four things to get right.
  • Never expose your encryption key.
  • Verify every signature.
  • HTTPS only.
  • Log what you receive — you will need it to reconcile.