The hierarchy (memorize this)
reference_id to your internal client ID. Most EMSs map 1:1 (one org per client), but you can split one client into multiple orgs if you need different webhook configs or card type filters.
Card Holder = a person. You set reference_id to your internal user ID. Two ways to onboard: email (user signs with eID) or identity_id (instant, person already in OpenCard). → Card Holder Onboarding
TPA (Transaction Processing Authorization) = legal doc the client’s signatories sign saying “yes, our card data can flow to this EMS via OpenCard.”
PDPC (Personal Data Processing Consent) = GDPR consent the individual cardholder signs saying “yes, my transactions can be shared.”
Two legal docs, two audiences
| Doc | Who signs | How | Purpose |
|---|---|---|---|
| TPA | Company’s authorized signatories (CEO, board, etc.) | eID sign mode — they sign the actual legal document | Company-level permission |
| PDPC | Individual cardholder (employee) | eID auth mode — identity verification + consent checkbox | Person-level GDPR consent |
identity_id and skip the wait entirely.
The full onboarding sequence
Runtime: what happens on a purchase
- Cardholder buys coffee ☕
- Issuer sends
authorizedtransaction state to OpenCard - OpenCard normalizes the data, checks org/webhook config
- Your webhook gets
card.transaction.authorizedwithin seconds - If merchant is receipt-enabled → OpenCard requests receipt match
- 3–5 days later issuer sends
clearedstate - Your webhook gets
card.transaction.cleared— this is the accounting truth - Maybe
receipt.fetched,transaction.true.vat,transaction.line_itemsfollow
What you build vs what OpenCard builds
| You build | OpenCard builds |
|---|---|
| Webhook endpoint + event handlers | Legal signing flows (TPA + PDPC) |
| Org/cardholder CRUD in your UI | eID signing flows |
Map reference_id to your DB | Issuer communication |
| Transaction UI in your expense app | Receipt matching orchestration |
| Webhook delivery + retry + logs |
Reference IDs — your best friend
Every organization and card holder has areference_id you define. Put your internal ID there. Every webhook payload includes it. Zero lookup tables needed.

