Context
At Xendit, permission checks were spread across many systems. User permissions lived in the legacy user service or were hard-coded in the dashboard. API key permissions lived in the API auth service or inside each domain service. Nothing gave one answer to “who may do what”, and each new cross-border product had to add its own checks. Those products also needed per-merchant control over 5 products and 140+ currency pairs.
What I did
As architect in a team of two, I designed and built a central authorization platform on OpenFGA:
- A modular model covering 5 financial products and 140+ currency pairs, with source-, destination- and pair-level permissions and real-time blocklist and whitelist overrides.
- A published check API, so product services ask “may this merchant do X?” and never see OpenFGA’s internals.
- An access control admin UI that Ops use during merchant onboarding, without engineering help.
- The migration with the strangler pattern: user authorization moved off the legacy user service first, then the cut-over went product by product with every product team, with zero downtime.
Results
- Every product API and the merchant dashboard depend on it.
- P99 under 40 ms in a 100 RPS load test.
- Ops grant and revoke access themselves.