Issuing verifiable credentials, reimagined.
A walkthrough of how Heirloom's credential issuance went from synchronous bulk-only to QR-driven auto-issuance with queue-backed bulk processing — and the architectural decisions that got us there.
01 — The artifact
A verifiable credential. Here's how it gets there.
HEIRLOOM 2024 EMPLOYEE ID
VERIFIABLE CREDENTIAL
VERIFIABLE CREDENTIAL
2024
This is a W3C Verifiable Credential — a tamper-evident, cryptographically-signed claim issued by an organization to a holder's wallet.
The card on the left is the artifact a user actually sees. The journey to get it there involves a dozen-plus services, a queue, a signing pipeline, and a DID resolution dance.
did:heirloom-polygon:371b3083-bbe5-46a3-ad4b...
02 — Three lanes
Issuance isn't one thing.
Upload a CSV with rows of recipient data. The system validates, dedupes, queues, and issues one credential per row.
Recipients
tens to thousands
Speed
minutes (queued)
Trigger
CSV
03 — Phase 1 pipeline
The original service dance.
- 01
Initiation
Organization sends a JWT-authenticated POST via the Credential Designer.
Creator FEServer - 02
Authorization & Collection Validation
Validate the collection, retrieve associated CredentialTypes (e.g. HLEventCredential).
Bulk Ingestion ServiceCollection Service - 03
Data Validation & Storage
Validate raw rows against the merged schema. Save IngestedData rows.
Validation ServiceIngest Validation ServicePostgres - 04
Row Processing
Fetch unprocessed rows (including retries from prior failures) and loop.
Bulk Ingestion Service - 05
Identity Management
Find or create the user via PropelAuth. Ensure they have a valid identity record.
User ServiceIdentity ServicePropelAuth - 06
Credential Offer Creation
Build claims, fetch templates, create the W3C VC document, write the Credential row.
Credential Issuance ServiceClaims BuilderCollection Service - 07
Credential Issuance
Derive keys, sign, create a credential challenge, email the recipient via customer.io.
Identity Key ServiceKey ServiceCredential Challenge Servicecustomer.io
04 — Where it broke
Synchronous, email-only, and people don't download new apps.
The original design was correct for the first job — bulk-issue credentials from a CSV. But the constraints that made v1 simple were exactly what blocked everything we wanted to do next.
Email-centric
Every credential rode an email. Recipients had to download an app, open the link, claim it. Drop-off was brutal.
Synchronous processing
Rows processed one at a time. A single hung row blocked the entire batch behind it.
Limited credential types
Schemas were hardcoded. Adding a new credential type required engineering work and a deploy.
Coupled notifications
Issuance, signing, and email delivery all happened in one synchronous call. Any failure cascaded.
05 — Phase 2 redesign
QR-driven auto-issuance, queue-backed bulk.
Auto issuance
QR-driven, cache-backed, ~150ms median.
- Phone scans QR
- POST /auto-issue
{ did, collectionId }
- Auto Issuance Service
- Identity Service⚡ redis
- Collection Service⚡ redis
- Credential Issuance Service
- Credential delivered
~150ms
Bulk issuance
Queue-backed, parallel workers, no recipient blocking.
- CSV uploaded
- Bulk Issuance Endpoint
- Validation Services → Postgres
- Enqueue jobs in BullMQ
- N workers pull concurrently
- Credential Issuance Flow per worker
- N credentials delivered
Auto issuance peels off the email-and-wait coupling: a recipient with a wallet just scans and resolves their DID against the issuer. Bulk no longer blocks per-row — the endpoint validates and enqueues, then BullMQ workers chew through the backlog in parallel.
06 — The payoff
Card lands. DID resolves.
HEIRLOOM 2024 EMPLOYEE ID
VERIFIABLE CREDENTIAL
VERIFIABLE CREDENTIAL
2024
did:heirloom-polygon:371b3083-bbe5-46a3-ad4b...
07 — Results
The numbers that justified the rebuild.
DAU lift
Daily active users in the first month after auto issuance shipped.
Credentials MoM
Month-over-month credential issuance volume after the redesign.
Avg issuance time
End-to-end auto issuance, cache-warm path. Cold path still sub-second.
DB load reduction
From the memory → Redis → Postgres caching hierarchy on hot reads.
Caching hierarchy
Hot reads — credential schemas, issuer keys, frequently-accessed QR codes — short-circuit at memory or Redis. The DB only sees writes and cold reads, which is what bought the 60% load reduction.