Apple's Alphabet Soup, and the Trust-Nobody Backend Behind Six Lines' Records
Building The Records, a one-card-a-day collectible inside Six Lines, meant staring down SIWA, ASC, JWS, and a sandbox gate. Each piece exists because of a fight Apple picked, and together they add up to a backend that trusts the phone about nothing.
The Records is a collectible archive inside Six Lines. You buy a volume once, a normal in-app purchase, and Volume I is The Warring States (戰國), 64 cards. But the archive doesn't hand you 64 cards. It hands you one a day.
Each card is a "file" pulled from the archive: a classical Chinese verse, a reading, a station (CHU STATION, QI STATION, named after the old Warring States kingdoms), and a pair of hexagram references, from_hex to to_hex, tying the card back to the I Ching transitions the whole app is built on. Card titles come straight from Warring States-era idioms and stratagems, "The Fox Borrows the Tiger," "Three Men, One Tiger." The framing is bureaucratic and a little occult: the Office opens its first file. Cards live in registers, which are just art editions, standard, collector, keeper, and a handful of sample cards stay permanently open so you can see what you're buying before you buy it.
Building the purchase flow for this is what surfaced the acronym soup. SIWA. ASC. JWS. A .p8 key I could apparently only download once. I asked Claude to walk me through why each piece exists instead of just what it does, and the history explained the design better than the docs did.
Sign in with Apple, 2019
Back in the late 2010s, "Log in with Facebook" and "Log in with Google" buttons were everywhere. Convenient, sure. But they also let Facebook and Google see which apps you used, and handed your real email to every developer who asked. Apple announced Sign in with Apple at WWDC 2019 as a direct answer to that. Same one-tap convenience, except Apple sits in the middle as the identity provider, and it ships with Hide My Email: a random relay address like x9k2m@privaterelay.appleid.com so the developer never sees your real inbox.
Apple didn't just ship it quietly. App Store guideline 4.8 says if your app offers any third-party login, it has to offer Sign in with Apple too. Developers complained, regulators took notice, and it went live anyway.
Underneath, it's OpenID Connect. Apple hands your app an identity token, a JWT it signs, and your backend checks that signature against Apple's published public keys. In the Records backend, that's the identity layer, and it's the only one: records_accounts keeps exactly one row per verified Apple subject. No device IDs, no phone numbers, nothing else counts as an account. It's also why account deletion needs to reauth with SIWA and revoke the token. Since 2022, Apple requires apps that let you create an account to let you delete it in-app too, and if the account was built on SIWA, deleting it means revoking those tokens with Apple, not just wiping a row in your own database.
App Store Connect used to be called something else
ASC is the developer portal. Manage listings, TestFlight builds, in-app purchase products, sales reports. Its name tells you where it came from: when the App Store launched in 2008, apps were sold through iTunes, so the portal was iTunes Connect. It got renamed App Store Connect in 2018.
What matters for Six Lines is the ASC API, added that same year. It's a REST API, authenticated with short-lived JWTs you sign yourself using a .p8 private key, an ES256 elliptic curve key you generate once in the portal and can download exactly once. Lose it, and you generate a new one. Apple never shows you the old one again.
The receipt problem, and why it got fixed in 2021
This is the part of the codebase appleVerifier.ts lives in, and it's the most interesting piece of the history.
From 2009, when in-app purchases launched with StoreKit, until 2021, purchase verification worked like this: the device got an opaque binary receipt, a PKCS#7 blob, and your server POSTed it to Apple's verifyReceipt endpoint and waited for JSON back. It worked, but it aged badly. This was the era of jailbreak tools feeding apps fake receipts, and servers that skipped verification handing out paid content for free because checking felt optional.
At WWDC 2021, Apple replaced the whole model, and this is what Six Lines actually uses:
StoreKit 2 transactions are JWS, JSON Web Signatures. Each transaction is a signed JSON payload with a certificate chain embedded in the header, rooted at one of Apple's root certificate authorities. If you're holding those root certificates, you can verify a transaction offline, with math, no call to Apple required. That's the entire reason appleVerifier.ts needs Apple's root certs inlined as base64. It walks the chain from the transaction's signing certificate up to a trusted root, the same trust model your browser uses for TLS.
The App Store Server API is the other half: a REST API your server calls to ask Apple directly about a purchase, transaction history, subscription status, refunds. Six Lines runs a daily reconcile cron against it, and that cron exists because of an old blind spot in the receipt model: if someone buys, deletes the app, then gets a refund, your server never hears about it unless you go ask. The cron sweeps Apple's records to catch exactly that case, and it only ever moves state forward, keyed on Apple's own signedDate, so a stale webhook arriving late can't resurrect a purchase that's already been revoked.
So when the env list has two Apple key pairs sitting next to each other, they're doing two separate jobs. One signs JWTs that prove identity to Apple's auth services, for SIWA token revocation. The other signs JWTs for the App Store Server API, for the reconcile cron. Same .p8 file format, not interchangeable, which is why .env.example has a warning not to mix them up.
Sandbox and production are cryptographically identical
Apple runs two parallel purchase worlds. Sandbox lets you test purchases with fake test accounts, no real money moves, and StoreKit fast-forwards time so a "one year" subscription renews in about an hour instead of making you wait a year to test renewal.
Sandbox transactions are signed by Apple exactly like real ones, and that's the part that actually matters. The only difference sitting in the payload is a single field, environment: "Sandbox". A production backend that checks the signature and stops there will happily grant paid content for a transaction that never cost anyone a dollar. That single field is the whole reason APPLE_ALLOWED_ENVIRONMENTS exists. Preview deploys accept Sandbox so I can test on a real device. Production is locked to Production only.
What all of that actually protects
Here's where the history stops being trivia and turns into the actual design of the archive.
The daily reveal is server-authoritative. The obvious way to build "one card a day" is to let the phone decide when a day has passed. The obvious way is also trivially cheated by rolling the device clock forward. So the server computes your local day itself, from a timezone offset the client sends, enforces a 20-hour minimum between reveals, and writes each one into a records_daily_keys table with a hard constraint: one key per account per day, full stop. The phone is never trusted about time.
Purchases are bound to an account, not a device. This is where SIWA and StoreKit 2 earn their keep. Buying a volume requires signing in with Apple first, because the account is the only thing the purchase can attach to. When the app reports a purchase, it sends the Apple-signed JWS transaction, and the server verifies that signature against Apple's root certificates, checks the environment field, and then binds the purchase to the account through an app_account_token, a UUID the server minted itself, which travels inside the Apple-signed transaction and therefore can't be forged or swapped after the fact. That binding is set once and never changes. Buy on one phone, restore on another, the account still owns it. The whole register-or-rebind-or-revoke decision runs inside a single Postgres function under a single lock, so two "restore purchases" taps at once can't race each other into a bad state.
Founding donors get grandfathered in through a primary key, not application logic. Anyone who donated to the qi pool before a cutoff date gets Volume I free. Claims aren't keyed by donation row, they're keyed by donor source (a device ID or an Apple ID). Three separate donations from the same source still resolve to exactly one founder coupon, because the constraint enforcing that lives in the database schema, not in some if statement that a future refactor could quietly break.
Account deletion actually deletes. Apple requires it, so it's real: reauthenticate with SIWA, attempt to revoke the tokens with Apple, scrub the stored Apple user ID, bump a session_version counter so every session token issued before that instant dies at once, then tombstone the row. Purchases survive as anonymous rows, stripped of anything that points back to a person, because the refund reconciliation cron still needs them to exist.
The layer I got wrong
I'd said this stack skips device attestation, proving the app instance itself is genuine and untampered rather than just proving a purchase happened. Turns out it doesn't skip it. Every request into the seven API routes (catalog, session, verify, reveal, me, reconcile, delete-account) passes through four checks in sequence: an API key, an HMAC signature over the request, a rate limit, and an optional App Attest check, Apple's own answer to "did this request really come from my unmodified app." The database itself sits behind Postgres row-level security locked to service-role only, so those seven routes are the entire surface area. Card art is served from a CDN under opaque 32-character keys, so even the image URLs leak nothing about which cards you haven't revealed yet.
The pattern underneath
Every piece of this is Apple closing a trust gap it opened itself, and the Records backend just refuses to trust the parts Apple hasn't closed either. The phone isn't trusted about time, so the server computes the day. It isn't trusted about purchases, so every transaction gets checked against Apple's own signature, not the client's word. It isn't trusted about identity, only a SIWA-verified subject counts as an account. And it isn't fully trusted about being the real app, so App Attest sits in the request path too.
The one-card-a-day ritual only means anything if it can't be skipped, so every rule that keeps it scarce lives on the server, and the app itself is just the window you look through to see it.