Open-source Open Badges 3.0 platform with Cloudflare Workers SaaS and Docker self-host runtime profiles.
Standards-compliant verifiable credential issuance and verification for educational achievements.
- Badge template creation - Define reusable badge designs with achievement criteria, images, and metadata
- Manual badge issuance - Issue badges to individual learners with signed verifiable credentials
- Cryptographic signing - Ed25519-based credential signing with
did:webissuer identities - Immutable storage - Store signed credentials as
.jsonldfiles in object storage (R2/S3) - Badge revocation - Revoke credentials with BitstringStatusList status tracking
- Email notifications - Automatic learner notifications on badge issuance
- Learner dashboard - View all earned badges in one place
- Governed learner pathways - Follow institution-defined, versioned programs with ordered badge and verified-record requirements
- Explainable progress - See exact evidence states, the next unmet requirement, and durable completion history
- Public badge pages - Shareable URLs for each credential with verification indicators
- Badge sharing - Copy URL, download VC JSON, add to LinkedIn profile directly
- Account linking - Link institutional identities with email fallback for post-graduation access
- Identity recovery - Maintain badge access after institutional email loss
- Multiple identities - Support for learner identity aliases across institutions
- Learner DID settings - Optional
did:key,did:web, ordid:ionsubject IDs for privacy-preserving issuance
- Public verification API -
/credentials/v1/{id}endpoint for third-party verification - Open Badges 3.0 - Full OB3 spec compliance with W3C Verifiable Credentials Data Model 2.0
- IMS Global validator - Deep-links to official IMS validator on badge pages
- Proof formats - Support for both VC-JWT and JSON-LD embedded proofs (EdDSA)
- Verifiable Presentations - VP create/verify APIs for holder-bound credential sharing
- DID documents - Serve
did:webdocuments for issuer public key discovery - Canonical URLs - OB3 JSON discovery links on public pages
- Achievement metadata - Rich achievement descriptions with evidence links
- Hosted authentication - Better Auth-backed passwordless email login through CredTrail wrapper routes
- Institutional SSO integration - Better Auth-backed institutional sign-in with learner identity linking
- Multi-tenant RBAC - Tenant-scoped roles: owner, admin, issuer
- Session management - Better Auth sessions with tenant-scoped authorization and secure cookie handling
- Guided onboarding - New tenant setup flow with first badge template and issuance walkthrough
- OAuth 2.0 flows - Authorization Code Grant with Dynamic Client Registration
- PKCE enforcement - S256 code challenge method required
- Service discovery -
/.well-known/openid-credential-issuerendpoint with OpenAPI documentation - Secure REST endpoints -
/ims/ob/v3p0/credentialsand/ims/ob/v3p0/profilewith scoped authorization - Token management - Refresh token rotation and revocation endpoints
- Content negotiation - Accept both VC-JWT and JSON-LD formats
- Credential upsert - Update existing credentials while maintaining immutability
- Audit logging - Immutable logs for issuance, revocation, and role changes
- Pathway governance - Publish immutable pathway versions, record approved exceptions, and require human review before final credential issuance
- Tenant isolation - Strict data separation between organizations
- Type safety - Full TypeScript with strict mode enabled
- Input validation - Zod schemas at all HTTP and queue boundaries
- Database-backed queues - Simple Postgres table for async jobs (issue_badge, revoke_badge)
- Monorepo structure - Turborepo-based workspace with shared packages
- CI/CD pipeline - Automated linting, type checking, and testing
- Observability - Structured JSON logging with Logpush export
- Runtime profiles - Cloudflare Workers (SaaS) and Node + Docker (self-host)
apps/api-worker: Primary Worker serving API + server-rendered Hono JSX UI.packages/core-domain: Shared domain models, VC signing, and cryptographic helpers.packages/db: Postgres query helpers, tenant scoping utilities, and DB-backed job queue storage.packages/validation: Zod schemas for HTTP and queue boundaries.docs: Implementation docs and ADRs (includingdocs/LEARNER_DID_SETUP.md,docs/LEARNER_WALLET_IMPORT.md,docs/DCC_LCW_COMPATIBILITY.md,docs/VERIFIABLE_PRESENTATIONS.md, anddocs/LMS_INDEPENDENCE_MIGRATION_RUNBOOK.md).
app.ts: Composition root only (dependency wiring + route registration).routes/: Route handlers by feature area.auth/: Tenant/session permission helpers.badges/: Issuance flow, public badge rendering/view models, revocation helpers.credentials/: Verification checks and proof verification helpers.signing/: Signing key/registry resolution, DID/JWKS docs, credential signer helpers.ob3/: Open Badges 3.0 OAuth/discovery/access-token helpers.http/: Common middleware and shared HTTP utility helpers.queue/: Queue job payload/building/processing/scheduled trigger helpers.
- Server-rendered app pages are authored in Hono JSX (
.tsx) and rendered throughappPage/renderAppPagefromapps/api-worker/src/ui/render-page.tsx. - Page modules return
AppPage; route handlers render them withrenderAppPage(c, page, status?). - Use page asset keys and
PageAssetsfor CSS and feature-local JavaScript. CSS and client script source strings live underapps/api-worker/src/ui/page-assets/content. - Do not add React, Kiwa, Tailwind, client components, hydration,
hono/html,raw(),dangerouslySetInnerHTML, or interpolated full-document HTML strings. - JSX owns text and attribute escaping for page markup. Validate boundary input with Zod and keep manual HTML escaping out of page modules.
- Queue messages are stored in Postgres table
job_queue_messages. - Use
POST /v1/jobs/processto lease and process pending jobs. - Hosted deployment can run the same processor via Cloudflare Cron scheduled events.
- Self-host deployment can run the queue polling worker process in Docker.
- Jobs:
issue_badge,revoke_badge,rebuild_verification_cache,import_migration_batch
Run from the workspace root:
pnpm export:ob3-openapipnpm check:ob3-openapipnpm build:design-tokenspnpm check:design-tokenspnpm lintpnpm lint:fixpnpm formatpnpm format:checkpnpm typecheckpnpm testpnpm check
OB3 OpenAPI snapshot file: docs/openapi/ims-ob-v3p0.openapi.json
Accessibility lint allows explicit native list and table roles: Safari can drop
list semantics after styling, and the mobile badge table uses block/grid layout.
Table containers and cells are excluded from control-label checks; their controls
are checked separately, and nested summary labels are inspected three levels deep.
prefer-tag-over-role is disabled because its replacements cannot represent
composite charts, inline SVG, action groups, or live status messages correctly.
Use Wrangler local with local Postgres and Wrangler R2 emulation for daily app development:
- First run:
pnpm dev:up - Daily Wrangler watch after bootstrap:
pnpm dev - Print health and demo routes:
pnpm dev:status - Print the seeded admin login URL:
pnpm dev:login-as - Reset and reseed demo data:
pnpm dev:reset - Watch a headed browser create real demo data:
pnpm dev:demo - Run browser regression checks:
pnpm test:e2e
The default seeded tenant is tenant_123; the seeded admin is
admin@credtrail.local. The seed includes a public credential DB row and local
Wrangler R2 object for /badges/trusted-demo-credential. See
docs/LOCAL_DEV.md for the full local demo contract and troubleshooting notes.
Copy .env.selfhost.example to .env.selfhost, then set your Postgres DATABASE_URL and
S3 or R2 bucket credentials. The default stack uses an existing database, such as AWS RDS:
docker compose --env-file .env.selfhost -f docker-compose.selfhost.yml up --build --waitFor local Postgres, set DATABASE_URL=postgres://credtrail:credtrail@postgres:5432/credtrail and add
-f docker-compose.selfhost-postgres.yml before up. See the
Docker runbook for certificate mounts, production settings,
backups, and validation. Docker installations can send email through SMTP or Amazon SES. The
SMTP setup and image email check cover mounted
passwords, Reply-To, and optional copies of badge notifications.
- Structured logs are emitted as JSON and can be exported through Logpush configuration.
See the integration API guide for issuing and revoking badges, tracking completion, discovering templates, and reconciling issued credentials with scoped API keys. The OpenAPI document is also served at /v1/programmatic/openapi.json.