Storing and handing over codes safely in your own system
7 min read
One endpoint serves a code and nothing else does. Encrypt it, hand it to one buyer, keep it out of every log — and know that there is no reissue to fall back on.
A code is the goods. It is issued once, it stays usable whatever any order record says afterwards, and there is no reissue and no recall — so the handling rules are short and they are all consequences of that: fetch it from the one endpoint that serves it, store it encrypted, hand it to exactly one buyer, and never let it reach a log, a notification, an analytics event or a support ticket. Everything below is those four in detail, and what to do when a customer says they lost it.
Where a code comes from, and the only place
GET /v1/orders/{number}, over a connection authenticated with your own key. That endpoint is the only one that serves a code, and the restriction is enforced rather than conventional:
-
The list endpoint never carries codes.
GET /v1/ordersreturns your orders without them, however you page it. -
No webhook carries a code, including
order.delivered. The notification travels to an address you chose through whatever proxies and logs sit in between; the code does not go that way. - On our side it is stored encrypted and hidden from serialisation, so it does not fall out of an object that was dumped for some other reason.
One thing to get right in your data model: an order can carry more than one code. A quantity of three of a card comes back as three entries in codes. Treat it as a list from the first line you write, not as a string you later have to migrate.
Fetch on the event, not on a poll
The clean sequence is: order.delivered arrives → you read the order by number → you store what came back → you hand it over. Polling every pending order on a timer works and costs you requests you do not need to spend; see webhooks and rate limits.
Fetch once and store the result. Re-reading the endpoint every time a customer opens their receipt turns one request into thousands and puts the code back on the wire for no reason.
At rest, in your system
Encrypt the column. A code sitting in plaintext in your orders table means a database dump, a stray backup, or a read-only analytics replica is a dump of sellable goods — and unlike a password there is nothing to reset afterwards.
Two further habits that cost nothing:
- Keep codes out of your ordinary order queries. A separate table, or a column excluded from your model's default selection, so a routine listing cannot accidentally carry them.
- Give them a retention rule. A code redeemed six months ago is a liability in your database rather than an asset. Decide when you delete, and write it down.
Where a code must never go
This list is worth reading literally, because every entry on it is a place codes actually end up:
- Request and response logging in your HTTP client. The most common leak by far: a debug-level logger that dumps every response body writes your inventory into a file with weaker access control than your database.
- Error trackers. An exception captured with the response attached carries the code to a third party's dashboard.
- Analytics and product events. Never as a property, never as an id.
- A webhook you forward. If you relay events to your own internal services, relay the order number and let each service fetch what it is allowed to.
- Support tickets and chat transcripts. Including the one where your agent pastes it to "check".
- A CSV export your staff can run. If a report has to prove a code was delivered, prove it with the order number and a timestamp.
Handing it over
- Write your own record first — order number, buyer, and the fact that a code was fetched — before you display anything. A code shown and not recorded is a code you cannot account for later.
- Show it to the buyer in the session that bought it, once.
- Make a second viewing deliberate: re-authenticate, and record who looked and when. It is the same code, so showing it again is fine — being unable to say who saw it is not.
- Send it through one channel. Not the page and the email and the message, because three copies are three places to leak from and three chances to send one to the wrong person.
- Never repost it into a group conversation, however convenient.
- Record who you handed it to. In a dispute this is the only evidence that exists on your side.
When a customer says it did not work
Almost always the code is fine and the redemption side is not. Work through it in order rather than assuming the goods are faulty, because the order is roughly the order of frequency:
- Compare what you sent with what you have. A code copied short is the single commonest cause, and it is the one you can settle without the customer.
- Check the country. The catalogue lists store cards by the country they were issued for, because the country is part of the goods rather than a description of them — a card issued for one country's store does not redeem in an account registered in another, and the message the customer sees usually says "invalid code", which is misleading. The code is valid; the account does not match it.
- Check it has not been used. A code is consumed once. If it went into two conversations, the first one spent it.
- Check how it is being entered — no stray whitespace, no characters picked up with the paste, nothing retyped by hand.
- Check where it is being entered. Some cards redeem in the account's own settings rather than at a payment screen inside a game.
- Then contact us with the order number, and never with the code in the message.
What is not available at the end of it is a replacement. Every code issued is final. There is no endpoint that asks for one back, no button in any dashboard, and no request you can make for one — because a code that has been handed over cannot be un-handed, and a path for asking would be a dispute channel over goods that cannot be returned.
The one state in which money does come back is fulfilment failing, and that is settled automatically: the order becomes refunded, its whole value is already in your wallet, and order.refunded reaches you. No code ever existed in that case, so there is nothing for your system to revoke.
The other half of the catalogue has no code at all
Most of what is on sale here is credited straight to an account, and that case is stricter still: there is nothing to hand over, nothing to store, and nothing to re-show — and credit that reached the wrong account cannot be pulled back by anybody. The field that decides it is collected in your checkout, which is why a checkout that collects the right account id is a separate guide, and why the two kinds of order is worth reading before you model either.
Questions
Can I fetch a code again months later?
Yes. GET /v1/orders/{number} keeps answering for your own orders, and the code does not expire on our side. That is a reason to build a deliberate re-view path rather than to keep a plaintext copy handy.
Should I show the code in the confirmation email?
Prefer a link back into your own account area over the code in the message body. An email is stored, forwarded, and synced to devices you do not control.
What if my own database is breached?
Encrypted at rest is the difference between a breach and a robbery. There is no revocation to fall back on, so the only defence is the one you build before it happens.
Do you keep a copy after delivery?
The order keeps it, encrypted, so you can read it back. It is served from one endpoint, to the account that bought it, and nowhere else.
Last updated