01 / Systems / Afriex
Cross-border payments
One app, many payment partners, one clear answer for the user.
A / Problem
Afriex moves money across borders through partner payment companies. Each partner has its own API, its own status codes and its own idea of what a webhook looks like. The user just wants to know if their money arrived.
B / Constraints
- A wrong status costs real money.
- Partners retry webhooks, go down, and change things with little warning.
- Business customers shouldn't see a partner's raw error message.
C / Design
I put each partner behind its own client with the same shape. The rest of the app asks "what's the status of this transfer?" and doesn't care who answers.
Webhooks all come in through one path: check the signature, translate the partner's status into ours, then update the transaction. If one fails, we can replay it. A webhook that arrives twice only counts once.
Deposits where the account name doesn't match the sender go to manual review instead of straight through. Partner errors map onto a short list of our own codes, so customers get something they can act on.
D / When it broke
In 2025 one partner's webhook signatures stopped validating. I switched that endpoint off in production rather than accept events we couldn't verify.
We were checking the signature against the parsed request body, but the partner signed the raw bytes. I added middleware to keep the raw body, fixed the check, and wrote tests for missing signature fields before switching it back on.
E / Trade-offs
A client per partner is more code than one generic one. When a partner changes something, though, the fix stays in one place.
F / Outcome
Adding a partner means writing one adapter, not touching the core flow.