Back to Projects
System RedesignDIDs • BullMQ • Postgres • Verifiable Credentials

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.

recipients.csv
N credentials

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.

  1. 01

    Initiation

    Organization sends a JWT-authenticated POST via the Credential Designer.

    Creator FEServer
  2. 02

    Authorization & Collection Validation

    Validate the collection, retrieve associated CredentialTypes (e.g. HLEventCredential).

    Bulk Ingestion ServiceCollection Service
  3. 03

    Data Validation & Storage

    Validate raw rows against the merged schema. Save IngestedData rows.

    Validation ServiceIngest Validation ServicePostgres
  4. 04

    Row Processing

    Fetch unprocessed rows (including retries from prior failures) and loop.

    Bulk Ingestion Service
  5. 05

    Identity Management

    Find or create the user via PropelAuth. Ensure they have a valid identity record.

    User ServiceIdentity ServicePropelAuth
  6. 06

    Credential Offer Creation

    Build claims, fetch templates, create the W3C VC document, write the Credential row.

    Credential Issuance ServiceClaims BuilderCollection Service
  7. 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.

Synchronous workerStalled
queue →
row 1 ✓
row 2 ✓
row 3 ⏳
row 4
row 5
row 6
→ done
row 3: EMAIL_SEND_TIMEOUTbacklog: 3 blocked

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.

  1. Phone scans QR
  2. POST /auto-issue

    { did, collectionId }

  3. Auto Issuance Service
  4. Identity Service⚡ redis
  5. Collection Service⚡ redis
  6. Credential Issuance Service
  7. Credential delivered

    ~150ms

Bulk issuance

Queue-backed, parallel workers, no recipient blocking.

  1. CSV uploaded
  2. Bulk Issuance Endpoint
  3. Validation Services → Postgres
  4. Enqueue jobs in BullMQ
  5. N workers pull concurrently
  6. Credential Issuance Flow per worker
  7. N credentials delivered
W1
W2
W3
W4

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.

0%

DAU lift

Daily active users in the first month after auto issuance shipped.

0%

Credentials MoM

Month-over-month credential issuance volume after the redesign.

~0ms

Avg issuance time

End-to-end auto issuance, cache-warm path. Cold path still sub-second.

0%

DB load reduction

From the memory → Redis → Postgres caching hierarchy on hot reads.

Caching hierarchy

memory ~1nsredis ~0.1mspostgres ~10ms

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.