W3C VC Status List v1.0
The rule
- Endpoint:
GET as.oid4pay.com/oauth/status-list/{list_id}returns a VC Status List Credential per W3C VC Status List v1.0. - Single list (
list_id=1) for v1; multiple lists once mandate count exceeds 100k. - Bitstring is gzip-compressed; one bit per mandate; bit position is
mandate.credentialStatus.statusListIndex. - Response headers:
Cache-Control: max-age=300, must-revalidate, public;ETagfor cheap re-fetch. - The AS regenerates the published list every 60s.
- Merchants and agents fetch the whole list once per 5 minutes and look up the index locally; no per-mandate round-trip.
Endpoint shape
GET /oauth/status-list/1 HTTP/1.1
Host: as.oid4pay.com
200 OK
Content-Type: application/vc+ld+json
Cache-Control: max-age=300, must-revalidate, public
ETag: "v123"
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://w3id.org/vc/status-list/2021/v1"
],
"id": "https://as.oid4pay.com/oauth/status-list/1",
"type": ["VerifiableCredential", "StatusList2021Credential"],
"issuer": "https://as.oid4pay.com",
"issuanceDate": "2026-05-14T10:00:00Z",
"credentialSubject": {
"id": "https://as.oid4pay.com/oauth/status-list/1#list",
"type": "StatusList2021",
"statusPurpose": "revocation",
"encodedList": "<base64url(gzip(bitstring))>"
},
"proof": { /* Ed25519 JWS proof */ }
}Mandate revocation linkage
Every mandate VC carries a credentialStatus claim:
"credentialStatus": {
"id": "https://as.oid4pay.com/oauth/status-list/1#42",
"type": "StatusList2021Entry",
"statusPurpose": "revocation",
"statusListIndex": "42",
"statusListCredential": "https://as.oid4pay.com/oauth/status-list/1"
}A merchant verifier decodes the gzipped bitstring once per fetch and checks bit 42. Bit set => revoked => reject the mandate.
Worked example
// Node: fetch once per 5 minutes and cache.
import { fetchStatusList } from "@oid4pay/oid4ac-merchant";
const list = await fetchStatusList("https://as.oid4pay.com", "1");
// list.bitmap is a Uint8Array
function isRevoked(statusListIndex) {
const byteIndex = Math.floor(statusListIndex / 8);
const bitIndex = statusListIndex % 8;
return (list.bitmap[byteIndex] >> bitIndex) & 1;
}
if (isRevoked(mandate.credentialStatus.statusListIndex)) {
throw new Error("mandate_status_revoked");
}Performance
100k mandates fit in 12.5 KB uncompressed and roughly 1 KB after gzip. A single fetch per merchant per
5 minutes is negligible; the If-None-Match revalidation makes the steady-state cost a 304 response.