From 0aa67c9a337fb28d9c4598afd340330095130ebc Mon Sep 17 00:00:00 2001 From: Derek Scruggs Date: Mon, 8 Jun 2026 11:46:02 -0500 Subject: [PATCH 1/4] Correct coordinator role on all build pages. The issuer and verifier build pages described a coordinator as running @bedrock/vc-delivery and hand-coding the raw VCALM exchange loop. That conflates two distinct roles. A coordinator is a client of a workflow service, not the service itself. Rewrite all six build pages (government, education, supply-chain; issuer and verifier) around the actual coordinator API surface: - Create an exchange: POST /exchanges with ttl + variables, receiving 204 plus a Location header (the exchange URL). - Host and share the interaction URL pointing the wallet at the exchange. - Poll GET /exchanges/ until state is complete and read results under exchange.variables.results. The workflow service (which runs @bedrock/vc-delivery) handles protocol negotiation, signing, verification, and multi-wallet interop. Request and response shapes verified against the bedrock-vc-delivery source. Co-Authored-By: Claude Opus 4.8 --- src/verticals/education/build/issuer.njk | 164 ++++++++------- src/verticals/education/build/verifier.njk | 185 +++++++++-------- src/verticals/government/build/issuer.njk | 167 +++++++++------- src/verticals/government/build/verifier.njk | 189 ++++++++++-------- src/verticals/supply-chain/build/issuer.njk | 166 ++++++++------- src/verticals/supply-chain/build/verifier.njk | 185 +++++++++-------- 6 files changed, 614 insertions(+), 442 deletions(-) diff --git a/src/verticals/education/build/issuer.njk b/src/verticals/education/build/issuer.njk index d6e0c0e..6546c4b 100644 --- a/src/verticals/education/build/issuer.njk +++ b/src/verticals/education/build/issuer.njk @@ -1,128 +1,158 @@ --- title: "Build an issuer — academic credentials" -description: "Issue diplomas over VCALM in 3 steps: start an exchange, deliver a signed VC, confirm. Server-side HTTP quick start." +description: "Issue diplomas as a VCALM coordinator: create an exchange, share an interaction URL, poll for the result. A workflow service does the protocol work." permalink: /education/build/issuer/ --- -{# Layer 4 — issuer coordinator quick start. The recommended path is the - @bedrock/vc-delivery server; the raw HTTP loop below shows what it runs. #} +{# Layer 4 — issuer coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It only creates + exchanges, hosts/shares interaction URLs, and polls for results. #}

Build an issuer

- You run the university side: start an exchange and deliver a signed diploma - credential into the graduate's wallet — over the same VCALM exchange loop - the wallet speaks. + You run the university side. To issue a diploma you don't speak VCALM on the + wire yourself — you drive a workflow service that does. As a + coordinator, your whole job is three calls: create an + exchange, share an interaction URL that points the graduate's wallet at it, + then poll the exchange until the diploma has been issued.

-

Under the hood: the raw VCALM exchange

-

- Want to build your own coordinator, or just see exactly what the server does - on the wire? It's a short HTTP loop — three things: -

+

What the coordinator does

    -
  1. Expose an interaction URL (in a QR code or link) that advertises - vcapi.
  2. -
  3. When the wallet POSTs to your exchange URL, respond with the - diploma in a verifiablePresentation.
  4. -
  5. Optionally ask the wallet to authenticate first (DID Auth), then - deliver; finish the exchange.
  6. +
  7. Create an exchange by POSTing the workflow's variables + to its exchanges endpoint.
  8. +
  9. Host and share an interaction URL that points the + graduate's wallet at that exchange.
  10. +
  11. Poll the exchange until it completes, then read the + result.
+

+ The workflow service does everything in between — protocol negotiation, + DID authentication, signing, talking to whatever wallet shows up — and hands + you back just the result. +

-

1. Advertise the exchange

+

1. Create the exchange

- The wallet dereferences your interaction URL (iuv=1) and you - return the protocols you support. Offer vcapi for VCALM: + POST to your workflow's exchanges endpoint. The body carries a + ttl and the variables the workflow expects — here, + who the diploma is for:

+ {% highlight "bash" %} +POST https://workflows.example/workflows/diploma-issuance/exchanges + {% endhighlight %} {% highlight "json" %} { - "protocols": { - "vcapi": "https://issuer.university.example/exchanges/diploma-789" + "ttl": 900, + "variables": { + "subject": "did:example:graduate123", + "degree": "B.Sc. Computer Science" } } {% endhighlight %} +

+ This call is authenticated (your service uses a capability or OAuth2 token). + On success the service returns 204 No Content with a + Location header — that's your exchange URL: +

+ {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/diploma-issuance/exchanges/z19xQ... + {% endhighlight %} -

2. Deliver the credential

+

2. Host and share the interaction URL

- The wallet POSTs an empty body to start. You respond with the signed diploma - wrapped in a verifiablePresentation: + The exchange URL is the vcapi interaction endpoint. + Hosting and sharing it is the coordinator's responsibility — put it in a QR + code or a link, typically wrapped in a presentation request the wallet + understands:

{% highlight "json" %} { - "verifiablePresentation": { - "@context": ["https://www.w3.org/ns/credentials/v2"], - "type": ["VerifiablePresentation"], - "verifiableCredential": [{ - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://w3id.org/education/v2" - ], - "type": ["VerifiableCredential", "UniversityDegreeCredential"], - "issuer": "did:web:university.example", - "credentialSubject": { - "id": "did:example:graduate123", - "degree": {"type": "BachelorDegree", "name": "B.Sc. Computer Science"} - }, - "proof": { "...": "issuer signature" } - }] + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/diploma-issuance/exchanges/z19xQ..." + }] + } } } {% endhighlight %}

- The exchange is complete when you return no - verifiablePresentationRequest. + The graduate's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. DID authentication and credential delivery happen + there — you are not in that loop.

-

3. (Optional) Authenticate the holder first

+

3. Poll for the result

- To bind the diploma to a holder, respond to the wallet's first POST with a - verifiablePresentationRequest for DID Authentication, then - deliver on the next turn: + While the wallet works, GET the exchange URL (authenticated) until its + state is complete:

+ {% highlight "bash" %} +GET https://workflows.example/workflows/diploma-issuance/exchanges/z19xQ... + {% endhighlight %} {% highlight "json" %} { - "verifiablePresentationRequest": { - "query": [{ - "type": "DIDAuthentication", - "acceptedMethods": [{"method": "example"}] - }], - "challenge": "99612b24-63d9-11ea-b99f-4f66f3e4f81a", - "domain": "issuer.university.example" + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "didAuthn": { + "did": "did:example:graduate123", + "verifiablePresentation": { "...": "the wallet's authenticated VP" } + } + } + } } } {% endhighlight %} +

+ Verified per-step results land under + exchange.variables.results, keyed by the workflow's step names. + A complete state means the diploma was issued into the + graduate's wallet. +

That's it

- A conformant education issuer is this exchange: advertise, deliver, confirm. - The signing of the credential itself is your issuer instance's job — the - coordinator just runs the exchange. + An education issuer, as a coordinator, is these three calls: create + an exchange, host and share the interaction URL, poll for the result. + Signing the diploma and speaking every wallet's protocol is the workflow + service's job — you just create exchanges and read what comes back.

On the wallet end, a standalone client-side VCALM library is still planned; - until it ships, the raw HTTP flow is the supported path there. For the - issuer/coordinator side shown here, use + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running @bedrock/vc-delivery.

Go deeper

    +
  • @bedrock/vc-delivery — workflow service
  • VCALM: DID Authentication
  • W3C Verifiable Credentials Data Model 2.0
  • VCALM specification
  • diff --git a/src/verticals/education/build/verifier.njk b/src/verticals/education/build/verifier.njk index 3553b53..650860e 100644 --- a/src/verticals/education/build/verifier.njk +++ b/src/verticals/education/build/verifier.njk @@ -1,135 +1,160 @@ --- title: "Build a verifier — academic credentials" -description: "Verify diplomas over VCALM in 3 steps: request a presentation, receive it, verify the proof. Server-side HTTP quick start." +description: "Verify diplomas as a VCALM coordinator: create an exchange, share an interaction URL, poll for the verified result. A workflow service does the protocol work." permalink: /education/build/verifier/ --- -{# Layer 4 — verifier coordinator quick start. The recommended path is the - @bedrock/vc-delivery server; the raw HTTP loop below shows what it runs. #} +{# Layer 4 — verifier coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It creates exchanges, + hosts/shares interaction URLs, and polls for verified results. #}

    Build a verifier

    - You run the admissions side: ask the applicant's wallet to present their - diploma, then verify the proof — over the same VCALM exchange loop the - wallet speaks. + You run the admissions side. You don't speak VCALM on the wire yourself; a + workflow service does that and verifies the diploma for you. As a + coordinator, your job is three calls: create an exchange, + share an interaction URL that points the applicant's wallet at it, then poll + until the verified result comes back.

    -

    Under the hood: the raw VCALM exchange

    -

    - Want to build your own coordinator, or just see exactly what the server does - on the wire? It's a short HTTP loop — three things: -

    +

    What the coordinator does

      -
    1. Expose an interaction URL that advertises vcapi.
    2. -
    3. When the wallet POSTs to start, respond with a - verifiablePresentationRequest describing the diploma you - need.
    4. -
    5. Receive the verifiablePresentation and verify its proof, - challenge, and domain.
    6. +
    7. Create an exchange against a workflow that requests the + diploma and names the institutions you trust.
    8. +
    9. Host and share an interaction URL that points the + applicant's wallet at that exchange.
    10. +
    11. Poll the exchange until it completes, then read the + verified result.
    +

    + The workflow service does the rest — it sends the presentation request, + receives the wallet's response, and verifies the proof, challenge, domain, + trusted issuer, and status before it ever returns a result to you. +

    -

    1. Request the diploma

    +

    1. Create the exchange

    - When the wallet POSTs an empty body to your exchange URL, respond with a - QueryByExample asking for the credential type you need: + POST to your workflow's exchanges endpoint. The workflow already + encodes request a university degree and trust this + institution; you pass just the per-exchange variables:

    + {% highlight "bash" %} +POST https://workflows.example/workflows/diploma-check/exchanges + {% endhighlight %} {% highlight "json" %} { - "verifiablePresentationRequest": { - "query": [{ - "type": "QueryByExample", - "credentialQuery": [{ - "reason": "Please present your university degree to continue your application.", - "example": { - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://w3id.org/education/v2" - ], - "type": "UniversityDegreeCredential" - }, - "trustedIssuer": [{ - "required": true, - "issuer": "did:web:university.example" - }] - }] - }], - "challenge": "3182bdea-63d9-11ea-b6de-3b7c1404d57f", - "domain": "admissions.example" + "ttl": 900, + "variables": { + "reason": "Please present your university degree to continue your application." } } {% endhighlight %}

    - The challenge and domain are what you'll verify in - the returned proof — they stop a captured presentation from being replayed. - Use trustedIssuer to require a degree from a specific - institution. + This call is authenticated. On success the service returns + 204 No Content with a Location header — your + exchange URL: +

    + {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/diploma-check/exchanges/z19xQ... + {% endhighlight %} +

    + The workflow's challenge and domain stop a captured + presentation from being replayed; its trustedIssuer setting + requires a degree from a specific institution.

    -

    2. Receive the presentation

    +

    2. Host and share the interaction URL

    - The wallet POSTs the signed presentation back to the same exchange URL: + The exchange URL is the vcapi interaction endpoint. + Hosting and sharing it is the coordinator's responsibility — put it in a QR + code or a link, typically wrapped in a presentation request:

    {% highlight "json" %} { - "verifiablePresentation": { - "@context": ["https://www.w3.org/ns/credentials/v2"], - "type": ["VerifiablePresentation"], - "holder": "did:example:graduate123", - "verifiableCredential": [{ "...": "the diploma VC" }], - "proof": { - "challenge": "3182bdea-63d9-11ea-b6de-3b7c1404d57f", - "domain": "admissions.example", - "...": "holder signature" + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/diploma-check/exchanges/z19xQ..." + }] } } } {% endhighlight %} +

    + The applicant's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. You are not in that loop. +

    -

    3. Verify the proof

    -

    Before you trust the diploma, check all of the following:

    -
      -
    • The presentation proof is valid and the - challenge / domain match what you sent.
    • -
    • Each credential's proof is valid and the issuer is - one you trust.
    • -
    • The credential is not revoked or suspended (check its - status).
    • -
    • The credential is unexpired and the type matches your - request.
    • -
    +

    3. Poll for the verified result

    +

    + GET the exchange URL (authenticated) until its state is + complete: +

    + {% highlight "bash" %} +GET https://workflows.example/workflows/diploma-check/exchanges/z19xQ... + {% endhighlight %} + {% highlight "json" %} +{ + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "diplomaCheck": { + "did": "did:example:graduate123", + "verifiablePresentation": { "...": "verified: the diploma VC" } + } + } + } + } +} + {% endhighlight %} +

    + A complete state means the workflow service already verified the + presentation for you — proof, challenge, domain, trusted issuer, and + revocation/expiry status. The verified result lands under + exchange.variables.results, keyed by the workflow's step names. +

    That's it

    - A conformant education verifier is this exchange: request, receive, verify. - The cryptographic verification itself is your verifier instance's job — the - coordinator just runs the exchange. + An education verifier, as a coordinator, is these three calls: create + an exchange, host and share the interaction URL, poll for the verified + result. The cryptographic verification and protocol interop are the + workflow service's job — you create exchanges and read what comes back.

    On the wallet end, a standalone client-side VCALM library is still planned; - until it ships, the raw HTTP flow is the supported path there. For the - verifier/coordinator side shown here, use + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running @bedrock/vc-delivery.

    Go deeper

      +
    • @bedrock/vc-delivery — workflow service
    • VCALM: QueryByExample
    • VCALM: Verifiable Presentation Request
    • VCALM specification
    • diff --git a/src/verticals/government/build/issuer.njk b/src/verticals/government/build/issuer.njk index b159028..2bb6f4e 100644 --- a/src/verticals/government/build/issuer.njk +++ b/src/verticals/government/build/issuer.njk @@ -1,125 +1,158 @@ --- title: "Build an issuer (DMV) — mobile driver's license" -description: "Issue a mobile driver's license over VCALM in 3 steps: start an exchange, deliver a signed VC, confirm. Server-side HTTP quick start." +description: "Issue a mobile driver's license as a VCALM coordinator: create an exchange, share an interaction URL, poll for the result. A workflow service does the protocol work." permalink: /government/build/issuer/ --- -{# Layer 4 — issuer coordinator. The recommended path is the - @bedrock/vc-delivery server; the raw HTTP loop below shows what it runs. #} +{# Layer 4 — issuer coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It only creates + exchanges, hosts/shares interaction URLs, and polls for results. #}

      Build an issuer

      - You run the DMV side: start an exchange and deliver a signed mobile license - into the resident's wallet — over the same VCALM exchange loop the wallet - speaks. + You run the DMV side. To issue a mobile license you don't speak VCALM on the + wire yourself — you drive a workflow service that does. As a + coordinator, your whole job is three calls: create an + exchange, share an interaction URL that points the resident's wallet at it, + then poll the exchange until the wallet has been issued the license.

      -

      Under the hood: the raw VCALM exchange

      -

      - Want to build your own coordinator, or just see exactly what the server does - on the wire? It's a short HTTP loop — three things: -

      +

      What the coordinator does

        -
      1. Expose an interaction URL (in a QR code or link) that advertises - vcapi.
      2. -
      3. Authenticate the resident, then deliver the license in a - verifiablePresentation.
      4. -
      5. Finish the exchange.
      6. +
      7. Create an exchange by POSTing the workflow's variables + to its exchanges endpoint.
      8. +
      9. Host and share an interaction URL that points the + resident's wallet at that exchange.
      10. +
      11. Poll the exchange until it completes, then read the + result.
      +

      + The workflow service does everything in between — protocol negotiation, + DID authentication, signing, talking to whatever wallet shows up — and hands + you back just the result. +

      -

      1. Advertise the exchange

      +

      1. Create the exchange

      - The wallet dereferences your interaction URL (iuv=1) and you - return the protocols you support. Offer vcapi for VCALM: + POST to your workflow's exchanges endpoint. The body carries a + ttl and the variables the workflow expects — here, + who the license is for:

      + {% highlight "bash" %} +POST https://workflows.example/workflows/mdl-issuance/exchanges + {% endhighlight %} {% highlight "json" %} { - "protocols": { - "vcapi": "https://issuer.dmv.state.example/exchanges/mdl-456" + "ttl": 900, + "variables": { + "subject": "did:example:resident123", + "document_number": "D1234567" } } {% endhighlight %} +

      + This call is authenticated (your service uses a capability or OAuth2 token). + On success the service returns 204 No Content with a + Location header — that's your exchange URL: +

      + {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ... + {% endhighlight %} -

      2. Authenticate, then deliver

      +

      2. Host and share the interaction URL

      - Identity credentials should be bound to the holder. Respond to the wallet's - first POST with a DID Authentication request: + The exchange URL is the vcapi interaction endpoint. + Hosting and sharing it is the coordinator's responsibility — put it in a QR + code or a link, typically wrapped in a presentation request the wallet + understands:

      {% highlight "json" %} { - "verifiablePresentationRequest": { - "query": [{ - "type": "DIDAuthentication", - "acceptedMethods": [{"method": "example"}] - }], - "challenge": "99612b24-63d9-11ea-b99f-4f66f3e4f81a", - "domain": "issuer.dmv.state.example" + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ..." + }] + } } } {% endhighlight %} -

      On the next turn, deliver the signed mobile license:

      +

      + The resident's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. DID authentication and credential delivery happen + there — you are not in that loop. +

      + +

      3. Poll for the result

      +

      + While the wallet works, GET the exchange URL (authenticated) until its + state is complete: +

      + {% highlight "bash" %} +GET https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ... + {% endhighlight %} {% highlight "json" %} { - "verifiablePresentation": { - "@context": ["https://www.w3.org/ns/credentials/v2"], - "type": ["VerifiablePresentation"], - "verifiableCredential": [{ - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://w3id.org/vc/mdl/v1" - ], - "type": ["VerifiableCredential", "Iso18013DriversLicenseCredential"], - "issuer": "did:web:dmv.state.example", - "credentialSubject": { - "id": "did:example:resident123", - "family_name": "Doe", - "given_name": "Jordan", - "birth_date": "1998-05-12", - "document_number": "D1234567", - "driving_privileges": [{"vehicle_category_code": "C"}] - }, - "proof": { "...": "DMV signature" } - }] + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "didAuthn": { + "did": "did:example:resident123", + "verifiablePresentation": { "...": "the wallet's authenticated VP" } + } + } + } } } {% endhighlight %}

      - The exchange is complete when you return no - verifiablePresentationRequest. + Verified per-step results land under + exchange.variables.results, keyed by the workflow's step names. + A complete state means the mobile license was issued into the + resident's wallet.

      That's it

      - A conformant DMV issuer is this exchange: advertise, authenticate, deliver, - confirm. Signing the credential is your issuer instance's job — the - coordinator just runs the exchange. + A DMV issuer, as a coordinator, is these three calls: create an + exchange, host and share the interaction URL, poll for the result. + Signing the license and speaking every wallet's protocol is the workflow + service's job — you just create exchanges and read what comes back.

      On the wallet end, a standalone client-side VCALM library is still planned; - until it ships, the raw HTTP flow is the supported path there. For the - issuer/coordinator side shown here, use + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running @bedrock/vc-delivery.

      Go deeper

        +
      • @bedrock/vc-delivery — workflow service
      • VCALM: DID Authentication
      • W3C Verifiable Credentials Data Model 2.0
      • VCALM specification
      • diff --git a/src/verticals/government/build/verifier.njk b/src/verticals/government/build/verifier.njk index 6778162..2881362 100644 --- a/src/verticals/government/build/verifier.njk +++ b/src/verticals/government/build/verifier.njk @@ -1,132 +1,163 @@ --- title: "Build a verifier (relying party) — mobile driver's license" -description: "Verify a mobile driver's license over VCALM in 3 steps: request, receive, verify. Selective disclosure — prove age without revealing identity. HTTP quick start." +description: "Verify a mobile driver's license as a VCALM coordinator: create an exchange, share an interaction URL, poll for the verified result. Selective disclosure — prove age without revealing identity." permalink: /government/build/verifier/ --- -{# Layer 4 — verifier coordinator. The recommended path is the - @bedrock/vc-delivery server; the raw HTTP loop below shows what it runs. - Highlights selective disclosure (prove "over 21" without birthdate). #} +{# Layer 4 — verifier coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It creates exchanges, + hosts/shares interaction URLs, and polls for verified results. The workflow + here requests only "over 21" — selective disclosure. #}

        Build a verifier

        - You run the relying-party side — a bar, an airport, a website. You ask the - resident's wallet to present just what you need, then verify the proof, over - the same VCALM exchange loop the wallet speaks. + You run the relying-party side — a bar, an airport, a website. You don't + speak VCALM on the wire yourself; a workflow service does that and + verifies the proof for you. As a coordinator, your job is + three calls: create an exchange, share an interaction URL that points the + resident's wallet at it, then poll until the verified result comes back.

        -

        Under the hood: the raw VCALM exchange

        -

        - Want to build your own coordinator, or just see exactly what the server does - on the wire? It's a short HTTP loop — three things: -

        +

        What the coordinator does

          -
        1. Expose an interaction URL that advertises vcapi.
        2. -
        3. When the wallet POSTs to start, respond with a - verifiablePresentationRequest for only the fields you need.
        4. -
        5. Receive the verifiablePresentation and verify its proof, - challenge, and domain.
        6. +
        7. Create an exchange against a workflow that requests only + what you need — here, proof of age.
        8. +
        9. Host and share an interaction URL that points the + resident's wallet at that exchange.
        10. +
        11. Poll the exchange until it completes, then read the + verified result.
        +

        + The workflow service does the rest — it sends the presentation request, + receives the wallet's response, and verifies the proof, challenge, domain, + issuer, and status before it ever returns a result to you. +

        -

        1. Request only what you need

        +

        1. Create the exchange

        - Don't ask for the whole license. Request a single attribute — here, proof of - age — so the resident reveals nothing more: + POST to your workflow's exchanges endpoint. The workflow already + encodes request only "over 21" and trust the DMV; you pass + just the per-exchange variables:

        + {% highlight "bash" %} +POST https://workflows.example/workflows/age-check/exchanges + {% endhighlight %} {% highlight "json" %} { - "verifiablePresentationRequest": { - "query": [{ - "type": "QueryByExample", - "credentialQuery": [{ - "reason": "Please prove you are over 21 to continue.", - "example": { - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://w3id.org/vc/mdl/v1" - ], - "type": "Iso18013DriversLicenseCredential" - }, - "trustedIssuer": [{ - "required": true, - "issuer": "did:web:dmv.state.example" - }] - }] - }], - "challenge": "3182bdea-63d9-11ea-b6de-3b7c1404d57f", - "domain": "checkout.example" + "ttl": 900, + "variables": { + "reason": "Please prove you are over 21 to continue." } } {% endhighlight %}

        - With selective disclosure, a well-built wallet returns a derived proof of - "over 21" — not the birthdate itself. The challenge and - domain stop a captured presentation from being replayed. + This call is authenticated. On success the service returns + 204 No Content with a Location header — your + exchange URL: +

        + {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/age-check/exchanges/z19xQ... + {% endhighlight %} +

        + Because the workflow asks for a single derived attribute, a well-built wallet + returns proof of "over 21" — not the birthdate. The workflow's + challenge and domain stop a captured presentation + from being replayed.

        -

        2. Receive the presentation

        -

        The wallet POSTs the signed presentation back to the same exchange URL:

        +

        2. Host and share the interaction URL

        +

        + The exchange URL is the vcapi interaction endpoint. + Hosting and sharing it is the coordinator's responsibility — put it in a QR + code or a link, typically wrapped in a presentation request: +

        {% highlight "json" %} { - "verifiablePresentation": { - "@context": ["https://www.w3.org/ns/credentials/v2"], - "type": ["VerifiablePresentation"], - "holder": "did:example:resident123", - "verifiableCredential": [{ "...": "derived proof: age_over_21 = true" }], - "proof": { - "challenge": "3182bdea-63d9-11ea-b6de-3b7c1404d57f", - "domain": "checkout.example", - "...": "holder signature" + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/age-check/exchanges/z19xQ..." + }] } } } {% endhighlight %} +

        + The resident's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. You are not in that loop. +

        -

        3. Verify the proof

        -

        Before you trust the result, check all of the following:

        -
          -
        • The presentation proof is valid and the - challenge / domain match what you sent.
        • -
        • The credential's proof is valid and the issuer is the - DMV you trust.
        • -
        • The license is not revoked or suspended — check its - status.
        • -
        • The license is unexpired and the disclosed claim - answers your request.
        • -
        +

        3. Poll for the verified result

        +

        + GET the exchange URL (authenticated) until its state is + complete: +

        + {% highlight "bash" %} +GET https://workflows.example/workflows/age-check/exchanges/z19xQ... + {% endhighlight %} + {% highlight "json" %} +{ + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "ageCheck": { + "did": "did:example:resident123", + "verifiablePresentation": { "...": "verified: age_over_21 = true" } + } + } + } + } +} + {% endhighlight %} +

        + A complete state means the workflow service already verified the + presentation for you — proof, challenge, domain, trusted issuer, and + revocation/expiry status. The verified result lands under + exchange.variables.results, keyed by the workflow's step names. +

        That's it

        - A conformant relying-party verifier is this exchange: request, receive, - verify. The cryptographic verification is your verifier instance's job — the - coordinator just runs the exchange. + A relying-party verifier, as a coordinator, is these three calls: + create an exchange, host and share the interaction URL, poll for the + verified result. The cryptographic verification and protocol interop + are the workflow service's job — you create exchanges and read what comes + back.

        On the wallet end, a standalone client-side VCALM library is still planned; - until it ships, the raw HTTP flow is the supported path there. For the - verifier/coordinator side shown here, use + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running @bedrock/vc-delivery.

        Go deeper

          +
        • @bedrock/vc-delivery — workflow service
        • VCALM: QueryByExample
        • VCALM: Verifiable Presentation Request
        • VCALM specification
        • diff --git a/src/verticals/supply-chain/build/issuer.njk b/src/verticals/supply-chain/build/issuer.njk index 787c3e2..e457707 100644 --- a/src/verticals/supply-chain/build/issuer.njk +++ b/src/verticals/supply-chain/build/issuer.njk @@ -1,130 +1,158 @@ --- title: "Build an issuer (brand) — product provenance" -description: "Issue authenticity credentials over VCALM in 3 steps: start an exchange, deliver a signed VC, confirm. Server-side HTTP quick start." +description: "Issue authenticity credentials as a VCALM coordinator: create an exchange, share an interaction URL, poll for the result. A workflow service does the protocol work." permalink: /supply-chain/build/issuer/ --- -{# Layer 4 — issuer coordinator. The recommended path is the - @bedrock/vc-delivery server; the raw HTTP loop below shows what it runs. #} +{# Layer 4 — issuer coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It only creates + exchanges, hosts/shares interaction URLs, and polls for results. #}

          Build an issuer

          - You run the brand side: start an exchange and deliver a signed authenticity - credential into the product's wallet — over the same VCALM exchange loop - the wallet speaks. + You run the brand side. To issue an authenticity credential you don't speak + VCALM on the wire yourself — you drive a workflow service that does. + As a coordinator, your whole job is three calls: create an + exchange, share an interaction URL that points the product's wallet at it, + then poll the exchange until the credential has been issued.

          -

          Under the hood: the raw VCALM exchange

          -

          - Want to build your own coordinator, or just see exactly what the server does - on the wire? It's a short HTTP loop — three things: -

          +

          What the coordinator does

            -
          1. Expose an interaction URL (on a tag, QR, or link) that advertises - vcapi.
          2. -
          3. When the wallet POSTs to your exchange URL, respond with the - authenticity credential in a verifiablePresentation.
          4. -
          5. Optionally bind the credential to the current owner first, then deliver; - finish the exchange.
          6. +
          7. Create an exchange by POSTing the workflow's variables + to its exchanges endpoint.
          8. +
          9. Host and share an interaction URL that points the + product's wallet at that exchange.
          10. +
          11. Poll the exchange until it completes, then read the + result.
          +

          + The workflow service does everything in between — protocol negotiation, + optional owner binding, signing, talking to whatever wallet shows up — and + hands you back just the result. +

          -

          1. Advertise the exchange

          +

          1. Create the exchange

          - The wallet dereferences your interaction URL (iuv=1) and you - return the protocols you support. Offer vcapi for VCALM: + POST to your workflow's exchanges endpoint. The body carries a + ttl and the variables the workflow expects — here, + which product the credential is for:

          + {% highlight "bash" %} +POST https://workflows.example/workflows/provenance-issuance/exchanges + {% endhighlight %} {% highlight "json" %} { - "protocols": { - "vcapi": "https://issuer.brand.example/exchanges/bag-456" + "ttl": 900, + "variables": { + "subject": "did:example:bag-456", + "sku": "HB-2026-0042" } } {% endhighlight %} +

          + This call is authenticated (your service uses a capability or OAuth2 token). + On success the service returns 204 No Content with a + Location header — that's your exchange URL: +

          + {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/provenance-issuance/exchanges/z19xQ... + {% endhighlight %} -

          2. Deliver the credential

          +

          2. Host and share the interaction URL

          - The wallet POSTs an empty body to start. You respond with the signed - authenticity credential wrapped in a verifiablePresentation: + The exchange URL is the vcapi interaction endpoint. + Hosting and sharing it is the coordinator's responsibility — put it on a tag, + QR code, or link, typically wrapped in a presentation request the wallet + understands:

          {% highlight "json" %} { - "verifiablePresentation": { - "@context": ["https://www.w3.org/ns/credentials/v2"], - "type": ["VerifiablePresentation"], - "verifiableCredential": [{ - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://w3id.org/traceability/v1" - ], - "type": ["VerifiableCredential", "ProductPassportCredential"], - "issuer": "did:web:brand.example", - "credentialSubject": { - "id": "did:example:bag-456", - "product": {"sku": "HB-2026-0042", "model": "Atelier Tote"}, - "origin": {"country": "FR", "facility": "Paris Atelier"}, - "manufacturedOn": "2026-03-14" - }, - "proof": { "...": "brand signature" } - }] + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/provenance-issuance/exchanges/z19xQ..." + }] + } } } {% endhighlight %}

          - The exchange is complete when you return no - verifiablePresentationRequest. + The product's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. Owner binding and credential delivery happen + there — you are not in that loop.

          -

          3. (Optional) Bind to the current owner first

          +

          3. Poll for the result

          - To tie the credential to whoever holds the product, respond to the wallet's - first POST with a verifiablePresentationRequest for DID - Authentication, then deliver on the next turn: + While the wallet works, GET the exchange URL (authenticated) until its + state is complete:

          + {% highlight "bash" %} +GET https://workflows.example/workflows/provenance-issuance/exchanges/z19xQ... + {% endhighlight %} {% highlight "json" %} { - "verifiablePresentationRequest": { - "query": [{ - "type": "DIDAuthentication", - "acceptedMethods": [{"method": "example"}] - }], - "challenge": "99612b24-63d9-11ea-b99f-4f66f3e4f81a", - "domain": "issuer.brand.example" + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "ownerBinding": { + "did": "did:example:bag-456", + "verifiablePresentation": { "...": "the wallet's authenticated VP" } + } + } + } } } {% endhighlight %} +

          + Verified per-step results land under + exchange.variables.results, keyed by the workflow's step names. + A complete state means the authenticity credential was issued + into the product's wallet. +

          That's it

          - A conformant provenance issuer is this exchange: advertise, deliver, confirm. - Signing the credential is your issuer instance's job — the coordinator just - runs the exchange. + A provenance issuer, as a coordinator, is these three calls: create + an exchange, host and share the interaction URL, poll for the result. + Signing the credential and speaking every wallet's protocol is the workflow + service's job — you just create exchanges and read what comes back.

          On the wallet end, a standalone client-side VCALM library is still planned; - until it ships, the raw HTTP flow is the supported path there. For the - issuer/coordinator side shown here, use + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running @bedrock/vc-delivery.

          Go deeper

            +
          • @bedrock/vc-delivery — workflow service
          • VCALM: DID Authentication
          • W3C Verifiable Credentials Data Model 2.0
          • VCALM specification
          • diff --git a/src/verticals/supply-chain/build/verifier.njk b/src/verticals/supply-chain/build/verifier.njk index 7df405d..af1fe29 100644 --- a/src/verticals/supply-chain/build/verifier.njk +++ b/src/verticals/supply-chain/build/verifier.njk @@ -1,135 +1,160 @@ --- title: "Build a verifier (buyer/retailer) — product provenance" -description: "Verify product authenticity over VCALM in 3 steps: request a presentation, receive it, verify the proof. Server-side HTTP quick start." +description: "Verify product authenticity as a VCALM coordinator: create an exchange, share an interaction URL, poll for the verified result. A workflow service does the protocol work." permalink: /supply-chain/build/verifier/ --- -{# Layer 4 — verifier coordinator. The recommended path is the - @bedrock/vc-delivery server; the raw HTTP loop below shows what it runs. #} +{# Layer 4 — verifier coordinator. A coordinator is a CLIENT of a workflow + service; it does not run @bedrock/vc-delivery itself. It creates exchanges, + hosts/shares interaction URLs, and polls for verified results. #}

            Build a verifier

            - You run the buyer or retailer side: ask the product's wallet to present its - authenticity credential, then verify the proof — over the same VCALM - exchange loop the wallet speaks. + You run the buyer or retailer side. You don't speak VCALM on the wire + yourself; a workflow service does that and verifies the credential + for you. As a coordinator, your job is three calls: create + an exchange, share an interaction URL that points the product's wallet at it, + then poll until the verified result comes back.

            -

            Under the hood: the raw VCALM exchange

            -

            - Want to build your own coordinator, or just see exactly what the server does - on the wire? It's a short HTTP loop — three things: -

            +

            What the coordinator does

              -
            1. Expose an interaction URL that advertises vcapi.
            2. -
            3. When the wallet POSTs to start, respond with a - verifiablePresentationRequest describing the credential you - need.
            4. -
            5. Receive the verifiablePresentation and verify its proof, - challenge, and domain.
            6. +
            7. Create an exchange against a workflow that requests the + authenticity credential and names the brands you trust.
            8. +
            9. Host and share an interaction URL that points the + product's wallet at that exchange.
            10. +
            11. Poll the exchange until it completes, then read the + verified result.
            +

            + The workflow service does the rest — it sends the presentation request, + receives the wallet's response, and verifies the proof, challenge, domain, + trusted issuer, and status before it ever returns a result to you. +

            -

            1. Request the credential

            +

            1. Create the exchange

            - When the wallet POSTs an empty body to your exchange URL, respond with a - QueryByExample asking for the credential type you need: + POST to your workflow's exchanges endpoint. The workflow already + encodes request a product passport and trust the real + brand; you pass just the per-exchange variables:

            + {% highlight "bash" %} +POST https://workflows.example/workflows/authenticity-check/exchanges + {% endhighlight %} {% highlight "json" %} { - "verifiablePresentationRequest": { - "query": [{ - "type": "QueryByExample", - "credentialQuery": [{ - "reason": "Please present this product's authenticity credential.", - "example": { - "@context": [ - "https://www.w3.org/ns/credentials/v2", - "https://w3id.org/traceability/v1" - ], - "type": "ProductPassportCredential" - }, - "trustedIssuer": [{ - "required": true, - "issuer": "did:web:brand.example" - }] - }] - }], - "challenge": "3182bdea-63d9-11ea-b6de-3b7c1404d57f", - "domain": "retailer.example" + "ttl": 900, + "variables": { + "reason": "Please present this product's authenticity credential." } } {% endhighlight %}

            - The challenge and domain are what you'll verify in - the returned proof — they stop a captured presentation from being replayed. - Use trustedIssuer to require the credential come from the real - brand. + This call is authenticated. On success the service returns + 204 No Content with a Location header — your + exchange URL: +

            + {% highlight "http" %} +HTTP/1.1 204 No Content +Location: https://workflows.example/workflows/authenticity-check/exchanges/z19xQ... + {% endhighlight %} +

            + The workflow's challenge and domain stop a captured + presentation from being replayed; its trustedIssuer setting + requires the credential come from the real brand.

            -

            2. Receive the presentation

            +

            2. Host and share the interaction URL

            - The wallet POSTs the signed presentation back to the same exchange URL: + The exchange URL is the vcapi interaction endpoint. + Hosting and sharing it is the coordinator's responsibility — put it on a tag, + QR code, or link, typically wrapped in a presentation request:

            {% highlight "json" %} { - "verifiablePresentation": { - "@context": ["https://www.w3.org/ns/credentials/v2"], - "type": ["VerifiablePresentation"], - "holder": "did:example:bag-456", - "verifiableCredential": [{ "...": "the authenticity VC" }], - "proof": { - "challenge": "3182bdea-63d9-11ea-b6de-3b7c1404d57f", - "domain": "retailer.example", - "...": "holder signature" + "VerifiablePresentation": { + "interact": { + "service": [{ + "type": "VerifiableCredentialApiExchangeService", + "serviceEndpoint": "https://workflows.example/workflows/authenticity-check/exchanges/z19xQ..." + }] } } } {% endhighlight %} +

            + The product's wallet POSTs to that endpoint and runs the exchange with the + workflow service directly. You are not in that loop. +

            -

            3. Verify the proof

            -

            Before you trust the product is genuine, check all of the following:

            -
              -
            • The presentation proof is valid and the - challenge / domain match what you sent.
            • -
            • The credential's proof is valid and the issuer is the - brand you trust.
            • -
            • The credential is not revoked (e.g. reported stolen or - recalled) — check its status.
            • -
            • The credential is unexpired and the type matches your - request.
            • -
            +

            3. Poll for the verified result

            +

            + GET the exchange URL (authenticated) until its state is + complete: +

            + {% highlight "bash" %} +GET https://workflows.example/workflows/authenticity-check/exchanges/z19xQ... + {% endhighlight %} + {% highlight "json" %} +{ + "exchange": { + "id": "z19xQ...", + "state": "complete", + "variables": { + "results": { + "authenticityCheck": { + "did": "did:example:bag-456", + "verifiablePresentation": { "...": "verified: the authenticity VC" } + } + } + } + } +} + {% endhighlight %} +

            + A complete state means the workflow service already verified the + presentation for you — proof, challenge, domain, trusted issuer, and + revocation/expiry status. The verified result lands under + exchange.variables.results, keyed by the workflow's step names. +

            That's it

            - A conformant provenance verifier is this exchange: request, receive, verify. - The cryptographic verification is your verifier instance's job — the - coordinator just runs the exchange. + A provenance verifier, as a coordinator, is these three calls: create + an exchange, host and share the interaction URL, poll for the verified + result. The cryptographic verification and protocol interop are the + workflow service's job — you create exchanges and read what comes back.

            On the wallet end, a standalone client-side VCALM library is still planned; - until it ships, the raw HTTP flow is the supported path there. For the - verifier/coordinator side shown here, use + until it ships, wallets run the exchange over the raw HTTP flow. The + coordinator side shown here works today against any VCALM workflow service, + such as one running @bedrock/vc-delivery.

            Go deeper

              +
            • @bedrock/vc-delivery — workflow service
            • VCALM: QueryByExample
            • VCALM: Verifiable Presentation Request
            • VCALM specification
            • From 6869e340d4e825e9d1a7f408a57a10d7e19a2ddc Mon Sep 17 00:00:00 2001 From: Derek Scruggs Date: Mon, 8 Jun 2026 12:46:40 -0500 Subject: [PATCH 2/4] Replace government vertical with student-ID vertical. Drop the DMV / mobile-driver's-license framing and repurpose the slot as a digital student ID vertical: a school issues, a student holds in the wallet of their choice, and a campus service or partner verifies. Student IDs are identity and entitlement credentials, kept distinct from the existing education (diploma/transcript) vertical. The selective- disclosure anchor becomes "currently enrolled" in place of "over 21". Verticals are collection-driven, so renaming the directory, the tag, and the permalinks updates the nav, footer, home grid, sitemap, and llms.txt automatically. The issuer and verifier pages keep the corrected coordinator model (create exchange, host/share interaction URL, poll for results) and are only re-themed. - Rename src/verticals/government -> src/verticals/student-id and its data file; change the collection tag to student-id. - Rewrite index.njk, build/index.njk, and build/wallet.njk around the student-ID story; re-theme build/issuer.njk and build/verifier.njk. - Rename flowIcons gov* keys to studentId* and update embedded labels. - Rename the unused government-flow.svg diagram to student-id-flow.svg. Co-Authored-By: Claude Opus 4.8 --- src/_data/flowIcons.js | 12 +- ...overnment-flow.svg => student-id-flow.svg} | 0 src/verticals/government/index.njk | 191 ----------------- .../build/index.njk | 22 +- .../build/issuer.njk | 46 ++-- .../build/verifier.njk | 58 +++--- .../build/wallet.njk | 26 +-- src/verticals/student-id/index.njk | 196 ++++++++++++++++++ .../student-id.json} | 2 +- 9 files changed, 279 insertions(+), 274 deletions(-) rename src/assets/diagrams/{government-flow.svg => student-id-flow.svg} (100%) delete mode 100644 src/verticals/government/index.njk rename src/verticals/{government => student-id}/build/index.njk (58%) rename src/verticals/{government => student-id}/build/issuer.njk (77%) rename src/verticals/{government => student-id}/build/verifier.njk (69%) rename src/verticals/{government => student-id}/build/wallet.njk (70%) create mode 100644 src/verticals/student-id/index.njk rename src/verticals/{government/government.json => student-id/student-id.json} (61%) diff --git a/src/_data/flowIcons.js b/src/_data/flowIcons.js index 0338249..2a669ab 100644 --- a/src/_data/flowIcons.js +++ b/src/_data/flowIcons.js @@ -88,7 +88,7 @@ export default { `, - govIssue: `

              {{ title }}

              -

              - Issue mobile driver's licenses and digital IDs that residents truly control — - verifiable in person or online, with the wallet of their choice, and without - locking citizens to a government-approved app. -

              -

              - To issue a mobile driver's license, a DMV signs it as a - W3C Verifiable Credential and delivers it to the resident's - wallet of choice over VCALM (the VC API). A relying party - verifies it cryptographically — in person or online — and with selective - disclosure the resident can prove one fact, like "over 21," without - revealing name, address, or birthdate. -

              - - -
              - {% set flowSteps = [ - {icon: flowIcons.govIssue, label: "DMV issues a mobile license", caption: "Resident picks their own wallet -- no state-mandated app"}, - {icon: flowIcons.govHold, label: "Resident holds and controls it", caption: "Nothing shared without one-tap, explicit consent"}, - {icon: flowIcons.govDisclose, label: "Proves one fact", caption: "Over 21 -- no name, address, or birthdate revealed"} - ] %} - {% set flowFooter = "Any conformant wallet -- no state-mandated app -- selective disclosure by design" %} - {% include "partials/flow-diagram.njk" %} -
              - -
              -

              What it feels like to use

              -

              - The technology is invisible by design. Here's what each person actually - does — and what they no longer have to. -

              - -
                -
              1. -

                The DMV issues a mobile license

                -

                - A resident requests their mobile driver's license from the DMV portal or - app. They authenticate once, pick the wallet they already use, and the - license lands in it — alongside their existing physical card. -

                -

                No state-mandated app. Their wallet, their choice.

                -
              2. -
              3. -

                The resident holds and controls it

                -

                - The license lives on their phone, under their control. Nothing is shared - automatically — every presentation needs an explicit, one-time approval - the resident sees and consents to. -

                -

                The state can't watch where it's used.

                -
              4. -
              5. -

                They prove just one thing, when needed

                -

                - At a bar, the resident taps to prove "over 21" — and reveals - nothing else. No name, no address, no birthdate. At an airport or a - website, they share exactly the fields asked for, and no more. -

                -

                Selective disclosure: minimum data, every time.

                -
              6. -
              - -
              -
              -

              {{ flowIcons.person | safe }}What the resident experiences

              -
                -
              • Their choice of wallet. Not a single government-approved app.
              • -
              • Prove a fact, not your identity. "Over 21" without your birthdate.
              • -
              • One-tap consent. Nothing leaves the phone without approval.
              • -
              • No tracking. The state doesn't see each place it's used.
              • -
              -
              -
              -

              {{ flowIcons.institution | safe }}What the agency experiences

              -
                -
              • Privacy by design. Data minimization is built into every request.
              • -
              • Instant, tamper-evident checks. No call-backs to the DMV.
              • -
              • No vendor or wallet lock-in. Works with every conformant wallet.
              • -
              • Standards-based. Built on open W3C Verifiable Credentials.
              • -
              -
              -
              -
              - -
              -

              How it works

              -

              One diagram, three actors, one loop.

              - -
              -     DMV                   RESIDENT              RELYING PARTY
              -  (issuer)             (wallet of choice)         (verifier)
              -     |                      |                          |
              -     | -- delivers -->      |                          |
              -     |  mobile license VC   |                          |
              -     |                      | <-- asks for --           |
              -     |                      |   proof of age / validity |
              -     |                      | -- presents -->           |
              -     |                      |   only what's needed      |
              -     |                      |                     verifies (ok)
              -  
              - -
                -
              • A mobile license is signed JSON the resident holds and controls.
              • -
              • - Delivery (DMV → resident) and - presentation (resident → verifier) are the same simple - exchange loop: POST, read what the server wants, POST back. -
              • -
              • - With selective disclosure, the resident can prove a single fact — - "over 21," "license valid" — without revealing name, address, or birthdate. -
              • -
              -
              - -
              -

              A good fit if…

              -
                -
              • You issue identity or entitlement credentials to the public.
              • -
              • You want residents to choose their own wallet, not an approved list.
              • -
              • You need selective disclosure — prove a fact without revealing everything.
              • -
              -

              - Start building -

              -
              - -
              -

              Common questions

              -
              -

              Can a digital ID prove age without revealing a birthdate?

              -

              - Yes. With selective disclosure, a resident proves a single fact — "over - 21," "license valid" — without revealing name, address, or birthdate. -

              - -

              Does a mobile driver's license lock residents to a government app?

              -

              - Not with VCALM — the license works with any conformant wallet. The - OpenID/mdoc approach often pairs with attestation and allow-lists that let - the state decide which apps are accepted. -

              - -

              Is a VCALM mobile driver's license private?

              -

              - Yes. Data minimization is built into every request, nothing is shared - without one-tap consent, and the state can't see where it's used. -

              - -

              Why VCALM vs. OID4?

              -

              - The OID4VCI / OID4VP, mdoc-centric approach often pairs with wallet - "attestation" and allow-lists — which means the DMV has to certify, and - build and maintain an integration for, every wallet it wants to support. - With VCALM, you issue once to an open standard and any conformant wallet - works, so there's no per-wallet integration to own. See - the full comparison. -

              -
              -
              diff --git a/src/verticals/government/build/index.njk b/src/verticals/student-id/build/index.njk similarity index 58% rename from src/verticals/government/build/index.njk rename to src/verticals/student-id/build/index.njk index add604c..5720baf 100644 --- a/src/verticals/government/build/index.njk +++ b/src/verticals/student-id/build/index.njk @@ -1,7 +1,7 @@ --- -title: "What are you building? — mobile driver's license & digital ID" -description: "Pick your role: wallet, issuer (DMV), or verifier (relying party). You only implement your piece." -permalink: /government/build/ +title: "What are you building? — digital student ID" +description: "Pick your role: wallet, issuer (school/registrar), or verifier (campus service or partner). You only implement your piece." +permalink: /student-id/build/ --- {# Layer 3 — role picker = conformance-class scoping. #}
              @@ -10,25 +10,25 @@ permalink: /government/build/
              • - +

                Wallet

                -

                The app holding the resident's mobile license.

                +

                The app holding the student's ID.

                You implement: an exchange client — POST, handle VPRs, return VPs.

                ~3 things, a few days

              • - -

                Issuer (DMV)

                -

                The agency issuing the mobile license.

                +
                +

                Issuer (school)

                +

                The registrar issuing the student ID.

                You implement: start an exchange, deliver a signed VC.

                small, separate piece

              • - -

                Verifier (relying party)

                -

                Whoever checks the license — bar, airport, website.

                +
                +

                Verifier (campus or partner)

                +

                Whoever checks the ID — library, lab door, partner venue.

                You implement: request a presentation, verify the proof.

                small, separate piece

                diff --git a/src/verticals/government/build/issuer.njk b/src/verticals/student-id/build/issuer.njk similarity index 77% rename from src/verticals/government/build/issuer.njk rename to src/verticals/student-id/build/issuer.njk index 2bb6f4e..a352c36 100644 --- a/src/verticals/government/build/issuer.njk +++ b/src/verticals/student-id/build/issuer.njk @@ -1,7 +1,7 @@ --- -title: "Build an issuer (DMV) — mobile driver's license" -description: "Issue a mobile driver's license as a VCALM coordinator: create an exchange, share an interaction URL, poll for the result. A workflow service does the protocol work." -permalink: /government/build/issuer/ +title: "Build an issuer (school) — digital student ID" +description: "Issue a digital student ID as a VCALM coordinator: create an exchange, share an interaction URL, poll for the result. A workflow service does the protocol work." +permalink: /student-id/build/issuer/ --- {# Layer 4 — issuer coordinator. A coordinator is a CLIENT of a workflow service; it does not run @bedrock/vc-delivery itself. It only creates @@ -9,11 +9,11 @@ permalink: /government/build/issuer/

                Build an issuer

                - You run the DMV side. To issue a mobile license you don't speak VCALM on the + You run the school side. To issue a student ID you don't speak VCALM on the wire yourself — you drive a workflow service that does. As a coordinator, your whole job is three calls: create an - exchange, share an interaction URL that points the resident's wallet at it, - then poll the exchange until the wallet has been issued the license. + exchange, share an interaction URL that points the student's wallet at it, + then poll the exchange until the wallet has been issued the student ID.

              • Poll the exchange until it completes, then read the result.
              • @@ -54,17 +54,17 @@ permalink: /government/build/issuer/

                POST to your workflow's exchanges endpoint. The body carries a ttl and the variables the workflow expects — here, - who the license is for: + who the student ID is for:

                {% highlight "bash" %} -POST https://workflows.example/workflows/mdl-issuance/exchanges +POST https://workflows.example/workflows/student-id-issuance/exchanges {% endhighlight %} {% highlight "json" %} { "ttl": 900, "variables": { - "subject": "did:example:resident123", - "document_number": "D1234567" + "subject": "did:example:student123", + "student_number": "S1234567" } } {% endhighlight %} @@ -75,7 +75,7 @@ POST https://workflows.example/workflows/mdl-issuance/exchanges

                {% highlight "http" %} HTTP/1.1 204 No Content -Location: https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ... +Location: https://workflows.example/workflows/student-id-issuance/exchanges/z19xQ... {% endhighlight %}

                2. Host and share the interaction URL

                @@ -91,14 +91,14 @@ Location: https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ... "interact": { "service": [{ "type": "VerifiableCredentialApiExchangeService", - "serviceEndpoint": "https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ..." + "serviceEndpoint": "https://workflows.example/workflows/student-id-issuance/exchanges/z19xQ..." }] } } } {% endhighlight %}

                - The resident's wallet POSTs to that endpoint and runs the exchange with the + The student's wallet POSTs to that endpoint and runs the exchange with the workflow service directly. DID authentication and credential delivery happen there — you are not in that loop.

                @@ -109,7 +109,7 @@ Location: https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ... state is complete:

                {% highlight "bash" %} -GET https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ... +GET https://workflows.example/workflows/student-id-issuance/exchanges/z19xQ... {% endhighlight %} {% highlight "json" %} { @@ -119,7 +119,7 @@ GET https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ... "variables": { "results": { "didAuthn": { - "did": "did:example:resident123", + "did": "did:example:student123", "verifiablePresentation": { "...": "the wallet's authenticated VP" } } } @@ -130,15 +130,15 @@ GET https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ...

                Verified per-step results land under exchange.variables.results, keyed by the workflow's step names. - A complete state means the mobile license was issued into the - resident's wallet. + A complete state means the student ID was issued into the + student's wallet.

                That's it

                - A DMV issuer, as a coordinator, is these three calls: create an + A school issuer, as a coordinator, is these three calls: create an exchange, host and share the interaction URL, poll for the result. - Signing the license and speaking every wallet's protocol is the workflow + Signing the student ID and speaking every wallet's protocol is the workflow service's job — you just create exchanges and read what comes back.

                @@ -158,5 +158,5 @@ GET https://workflows.example/workflows/mdl-issuance/exchanges/z19xQ...
              • VCALM specification
              -

              ← Back to roles

              +

              ← Back to roles

              diff --git a/src/verticals/government/build/verifier.njk b/src/verticals/student-id/build/verifier.njk similarity index 69% rename from src/verticals/government/build/verifier.njk rename to src/verticals/student-id/build/verifier.njk index 2881362..691f17f 100644 --- a/src/verticals/government/build/verifier.njk +++ b/src/verticals/student-id/build/verifier.njk @@ -1,20 +1,20 @@ --- -title: "Build a verifier (relying party) — mobile driver's license" -description: "Verify a mobile driver's license as a VCALM coordinator: create an exchange, share an interaction URL, poll for the verified result. Selective disclosure — prove age without revealing identity." -permalink: /government/build/verifier/ +title: "Build a verifier (campus or partner) — digital student ID" +description: "Verify a digital student ID as a VCALM coordinator: create an exchange, share an interaction URL, poll for the verified result. Selective disclosure — prove enrollment without revealing identity." +permalink: /student-id/build/verifier/ --- {# Layer 4 — verifier coordinator. A coordinator is a CLIENT of a workflow service; it does not run @bedrock/vc-delivery itself. It creates exchanges, hosts/shares interaction URLs, and polls for verified results. The workflow - here requests only "over 21" — selective disclosure. #} + here requests only "currently enrolled" — selective disclosure. #}

              Build a verifier

              - You run the relying-party side — a bar, an airport, a website. You don't - speak VCALM on the wire yourself; a workflow service does that and - verifies the proof for you. As a coordinator, your job is - three calls: create an exchange, share an interaction URL that points the - resident's wallet at it, then poll until the verified result comes back. + You run the relying-party side — a library, a lab door, a partner venue. You + don't speak VCALM on the wire yourself; a workflow service does that + and verifies the proof for you. As a coordinator, your job + is three calls: create an exchange, share an interaction URL that points the + student's wallet at it, then poll until the verified result comes back.

              @@ -39,9 +39,9 @@ permalink: /government/build/verifier/

              What the coordinator does

              1. Create an exchange against a workflow that requests only - what you need — here, proof of age.
              2. + what you need — here, proof of enrollment.
              3. Host and share an interaction URL that points the - resident's wallet at that exchange.
              4. + student's wallet at that exchange.
              5. Poll the exchange until it completes, then read the verified result.
              @@ -54,17 +54,17 @@ permalink: /government/build/verifier/

              1. Create the exchange

              POST to your workflow's exchanges endpoint. The workflow already - encodes request only "over 21" and trust the DMV; you pass - just the per-exchange variables: + encodes request only "currently enrolled" and trust the + school; you pass just the per-exchange variables:

              {% highlight "bash" %} -POST https://workflows.example/workflows/age-check/exchanges +POST https://workflows.example/workflows/enrollment-check/exchanges {% endhighlight %} {% highlight "json" %} { "ttl": 900, "variables": { - "reason": "Please prove you are over 21 to continue." + "reason": "Please prove you are currently enrolled to continue." } } {% endhighlight %} @@ -75,13 +75,13 @@ POST https://workflows.example/workflows/age-check/exchanges

              {% highlight "http" %} HTTP/1.1 204 No Content -Location: https://workflows.example/workflows/age-check/exchanges/z19xQ... +Location: https://workflows.example/workflows/enrollment-check/exchanges/z19xQ... {% endhighlight %}

              Because the workflow asks for a single derived attribute, a well-built wallet - returns proof of "over 21" — not the birthdate. The workflow's - challenge and domain stop a captured presentation - from being replayed. + returns proof of "currently enrolled" — not the name, photo, or student + number. The workflow's challenge and domain stop a + captured presentation from being replayed.

              2. Host and share the interaction URL

              @@ -96,14 +96,14 @@ Location: https://workflows.example/workflows/age-check/exchanges/z19xQ... "interact": { "service": [{ "type": "VerifiableCredentialApiExchangeService", - "serviceEndpoint": "https://workflows.example/workflows/age-check/exchanges/z19xQ..." + "serviceEndpoint": "https://workflows.example/workflows/enrollment-check/exchanges/z19xQ..." }] } } } {% endhighlight %}

              - The resident's wallet POSTs to that endpoint and runs the exchange with the + The student's wallet POSTs to that endpoint and runs the exchange with the workflow service directly. You are not in that loop.

              @@ -113,7 +113,7 @@ Location: https://workflows.example/workflows/age-check/exchanges/z19xQ... complete:

              {% highlight "bash" %} -GET https://workflows.example/workflows/age-check/exchanges/z19xQ... +GET https://workflows.example/workflows/enrollment-check/exchanges/z19xQ... {% endhighlight %} {% highlight "json" %} { @@ -122,9 +122,9 @@ GET https://workflows.example/workflows/age-check/exchanges/z19xQ... "state": "complete", "variables": { "results": { - "ageCheck": { - "did": "did:example:resident123", - "verifiablePresentation": { "...": "verified: age_over_21 = true" } + "enrollmentCheck": { + "did": "did:example:student123", + "verifiablePresentation": { "...": "verified: enrolled = true" } } } } @@ -163,5 +163,5 @@ GET https://workflows.example/workflows/age-check/exchanges/z19xQ...
            • VCALM specification
            -

            ← Back to roles

            +

            ← Back to roles

            diff --git a/src/verticals/government/build/wallet.njk b/src/verticals/student-id/build/wallet.njk similarity index 70% rename from src/verticals/government/build/wallet.njk rename to src/verticals/student-id/build/wallet.njk index 03bb735..1100e92 100644 --- a/src/verticals/government/build/wallet.njk +++ b/src/verticals/student-id/build/wallet.njk @@ -1,12 +1,12 @@ --- -title: "VCALM tutorial: build a wallet — mobile driver's license" -description: "Make your wallet speak VCALM in 3 steps: dereference, POST to start, loop. HTTP example for mobile driver's licenses and digital ID." -permalink: /government/build/wallet/ +title: "VCALM tutorial: build a wallet — digital student ID" +description: "Make your wallet speak VCALM in 3 steps: dereference, POST to start, loop. HTTP example for digital student IDs." +permalink: /student-id/build/wallet/ --- -{# Layer 4 — exchange client. Same VCALM loop, mDL credential. #} +{# Layer 4 — exchange client. Same VCALM loop, student-ID credential. #}

            Build a wallet

            -

            Make your wallet speak VCALM: pick up and present a mobile license over an exchange.

            +

            Make your wallet speak VCALM: pick up and present a student ID over an exchange.

            You'll do exactly 3 things

              @@ -19,15 +19,15 @@ permalink: /government/build/wallet/

              1. Discover protocols

              A scanned QR code encodes an interaction URL with iuv=1. GET it:

              {% highlight "http" %} -GET /interactions/mdl-456?iuv=1 -Host: dmv.state.example +GET /interactions/student-id-456?iuv=1 +Host: registrar.university.example Accept: application/json {% endhighlight %}

              The response lists supported protocols; pick vcapi for VCALM:

              {% highlight "json" %} { "protocols": { - "vcapi": "https://vcapi.dmv.state.example/exchanges/mdl-456" + "vcapi": "https://vcapi.registrar.university.example/exchanges/student-id-456" } } {% endhighlight %} @@ -35,8 +35,8 @@ Accept: application/json

              2. Start the exchange

              POST an empty body to the vcapi URL:

              {% highlight "http" %} -POST /exchanges/mdl-456 -Host: vcapi.dmv.state.example +POST /exchanges/student-id-456 +Host: vcapi.registrar.university.example Content-Type: application/json {} @@ -45,7 +45,7 @@ Content-Type: application/json

              3. Run the loop

              When the server returns a verifiablePresentationRequest, find - the matching license, build a signed Verifiable Presentation — disclosing + the matching student ID, build a signed Verifiable Presentation — disclosing only the requested fields — and POST it back to the same exchange URL. When the server returns a verifiablePresentation, store the delivered credential. The exchange is done when the server stops @@ -53,7 +53,7 @@ Content-Type: application/json

              That's it

              -

              A conformant mobile-license wallet is this loop. Nothing more.

              +

              A conformant student-ID wallet is this loop. Nothing more.

              Prefer a library to raw HTTP? @@ -69,5 +69,5 @@ Content-Type: application/json
            1. VCALM specification
          -

          ← Back to roles

          +

          ← Back to roles

          diff --git a/src/verticals/student-id/index.njk b/src/verticals/student-id/index.njk new file mode 100644 index 0000000..421b067 --- /dev/null +++ b/src/verticals/student-id/index.njk @@ -0,0 +1,196 @@ +--- +title: "Student IDs & campus credentials with VCALM" +shortName: "Student IDs" +cardTitle: "Student IDs & campus credentials" +summary: "Issue student IDs students control — verifiable across campus and beyond, with any wallet." +description: "Issue digital student IDs with W3C Verifiable Credentials over VCALM — student-controlled, any wallet, no allow-lists, privacy by design. Prove enrollment without revealing identity." +order: 3 +permalink: /student-id/ +--- +{# Single-page student-ID vertical. Anchor: digital campus/student ID. + Distinct from the education (diploma) vertical — this is identity and + entitlement, not transcripts. Lean on wallet-choice / no-allow-list / + privacy — the student-liberty angle. #} + +
          +

          {{ title }}

          +

          + Issue student IDs that students truly control — verifiable across campus and + with partners, in person or online, with the wallet of their choice, and + without locking students to a school-approved app. +

          +

          + To issue a digital student ID, a school signs it as a + W3C Verifiable Credential and delivers it to the student's + wallet of choice over VCALM (the VC API). A campus service + or partner verifies it cryptographically — in person or online — and with + selective disclosure the student can prove one fact, like "currently + enrolled," without revealing their name, photo, or student number. +

          +
          + +
          + {% set flowSteps = [ + {icon: flowIcons.studentIdIssue, label: "School issues a student ID", caption: "Student picks their own wallet -- no school-mandated app"}, + {icon: flowIcons.studentIdHold, label: "Student holds and controls it", caption: "Nothing shared without one-tap, explicit consent"}, + {icon: flowIcons.studentIdDisclose, label: "Proves one fact", caption: "Currently enrolled -- no name, photo, or student number revealed"} + ] %} + {% set flowFooter = "Any conformant wallet -- no school-mandated app -- selective disclosure by design" %} + {% include "partials/flow-diagram.njk" %} +
          + +
          +

          What it feels like to use

          +

          + The technology is invisible by design. Here's what each person actually + does — and what they no longer have to. +

          + +
            +
          1. +

            The school issues a student ID

            +

            + A student requests their digital ID from the registrar portal or campus + app. They authenticate once, pick the wallet they already use, and the + ID lands in it — alongside their physical card. +

            +

            No school-mandated app. Their wallet, their choice.

            +
          2. +
          3. +

            The student holds and controls it

            +

            + The student ID lives on their phone, under their control. Nothing is + shared automatically — every presentation needs an explicit, one-time + approval the student sees and consents to. +

            +

            The school can't watch where it's used.

            +
          4. +
          5. +

            They prove just one thing, when needed

            +

            + At the library or a lab door, the student taps to prove "currently + enrolled" — and reveals nothing else. No name, no photo, no student + number. At a partner venue offering a student discount, they share + exactly the field asked for, and no more. +

            +

            Selective disclosure: minimum data, every time.

            +
          6. +
          + +
          +
          +

          {{ flowIcons.person | safe }}What the student experiences

          +
            +
          • Their choice of wallet. Not a single school-approved app.
          • +
          • Prove a fact, not your identity. "Currently enrolled" without your student number.
          • +
          • One-tap consent. Nothing leaves the phone without approval.
          • +
          • No tracking. The school doesn't see each place it's used.
          • +
          +
          +
          +

          {{ flowIcons.institution | safe }}What the school experiences

          +
            +
          • Privacy by design. Data minimization is built into every request.
          • +
          • Instant, tamper-evident checks. No call-backs to the registrar.
          • +
          • No vendor or wallet lock-in. Works with every conformant wallet.
          • +
          • Standards-based. Built on open W3C Verifiable Credentials.
          • +
          +
          +
          +
          + +
          +

          How it works

          +

          One diagram, three actors, one loop.

          + +
          +     SCHOOL                STUDENT              CAMPUS / PARTNER
          +    (issuer)           (wallet of choice)          (verifier)
          +     |                      |                          |
          +     | -- delivers -->      |                          |
          +     |  student ID VC       |                          |
          +     |                      | <-- asks for --           |
          +     |                      |   proof of enrollment     |
          +     |                      | -- presents -->           |
          +     |                      |   only what's needed      |
          +     |                      |                     verifies (ok)
          +  
          + +
            +
          • A student ID is signed JSON the student holds and controls.
          • +
          • + Delivery (school → student) and + presentation (student → verifier) are the same simple + exchange loop: POST, read what the server wants, POST back. +
          • +
          • + With selective disclosure, the student can prove a single fact — + "currently enrolled," "valid student" — without revealing name, photo, or + student number. +
          • +
          +
          + +
          +

          A good fit if…

          +
            +
          • You issue identity or entitlement credentials to students.
          • +
          • You want students to choose their own wallet, not an approved list.
          • +
          • You need selective disclosure — prove a fact without revealing everything.
          • +
          +

          + Start building +

          +
          + +
          +

          Common questions

          +
          +

          Can a student ID prove enrollment without revealing identity?

          +

          + Yes. With selective disclosure, a student proves a single fact — + "currently enrolled," "valid student" — without revealing name, photo, or + student number. +

          + +

          Does a digital student ID lock students to a school app?

          +

          + Not with VCALM — the student ID works with any conformant wallet. The + OpenID/mdoc approach often pairs with attestation and allow-lists that let + the school decide which apps are accepted. +

          + +

          Is a VCALM student ID private?

          +

          + Yes. Data minimization is built into every request, nothing is shared + without one-tap consent, and the school can't see where it's used. +

          + +

          Why VCALM vs. OID4?

          +

          + The OID4VCI / OID4VP, mdoc-centric approach often pairs with wallet + "attestation" and allow-lists — which means the school has to certify, and + build and maintain an integration for, every wallet it wants to support. + With VCALM, you issue once to an open standard and any conformant wallet + works, so there's no per-wallet integration to own. See + the full comparison. +

          +
          +
          diff --git a/src/verticals/government/government.json b/src/verticals/student-id/student-id.json similarity index 61% rename from src/verticals/government/government.json rename to src/verticals/student-id/student-id.json index f141cce..d8c3cab 100644 --- a/src/verticals/government/government.json +++ b/src/verticals/student-id/student-id.json @@ -1,4 +1,4 @@ { "layout": "layouts/base.njk", - "tags": "government" + "tags": "student-id" } From e4c11423006d2ff12c13d6349b355218f5863356 Mon Sep 17 00:00:00 2001 From: Derek Scruggs Date: Mon, 8 Jun 2026 12:49:07 -0500 Subject: [PATCH 3/4] Clarify that coordinators speak VCALM, not VCALM delivery. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The build pages said a coordinator does not "speak VCALM on the wire." That implies the coordinator is outside VCALM entirely. It is not — the create-exchange / host-interaction-URL / poll API is itself the coordinator side of VCALM. Reword the six issuer/verifier intros: a coordinator speaks VCALM, just not VCALM delivery — the on-the-wire exchange loop with the wallet, which the workflow service handles. Also scope "the protocol mess" to "VCALM delivery" rather than VCALM as a whole. Co-Authored-By: Claude Opus 4.8 --- src/verticals/education/build/issuer.njk | 9 +++++---- src/verticals/education/build/verifier.njk | 10 ++++++---- src/verticals/student-id/build/issuer.njk | 9 +++++---- src/verticals/student-id/build/verifier.njk | 10 ++++++---- src/verticals/supply-chain/build/issuer.njk | 9 +++++---- src/verticals/supply-chain/build/verifier.njk | 10 ++++++---- 6 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/verticals/education/build/issuer.njk b/src/verticals/education/build/issuer.njk index 6546c4b..3d540d8 100644 --- a/src/verticals/education/build/issuer.njk +++ b/src/verticals/education/build/issuer.njk @@ -9,9 +9,10 @@ permalink: /education/build/issuer/

          Build an issuer

          - You run the university side. To issue a diploma you don't speak VCALM on the - wire yourself — you drive a workflow service that does. As a - coordinator, your whole job is three calls: create an + You run the university side. As a coordinator you speak + VCALM — just the coordinator side of it. You don't run VCALM + delivery, the on-the-wire exchange with the graduate's wallet; a + workflow service does that. Your whole job is three calls: create an exchange, share an interaction URL that points the graduate's wallet at it, then poll the exchange until the diploma has been issued.

          @@ -20,7 +21,7 @@ permalink: /education/build/issuer/

          Recommended

          Drive a workflow service — don't build the protocol

          - The protocol mess — VCALM, OID4VCI, OID4VP, and interoperating with + The protocol mess — VCALM delivery, OID4VCI, OID4VP, and interoperating with whatever wallet a graduate brings — is handled by a workflow service. Digital Bazaar's @bedrock/vc-delivery diff --git a/src/verticals/education/build/verifier.njk b/src/verticals/education/build/verifier.njk index 650860e..4ac9f81 100644 --- a/src/verticals/education/build/verifier.njk +++ b/src/verticals/education/build/verifier.njk @@ -9,9 +9,11 @@ permalink: /education/build/verifier/

          Build a verifier

          - You run the admissions side. You don't speak VCALM on the wire yourself; a - workflow service does that and verifies the diploma for you. As a - coordinator, your job is three calls: create an exchange, + You run the admissions side. As a coordinator you speak + VCALM — just the coordinator side of it. You don't run VCALM + delivery, the on-the-wire exchange with the applicant's wallet; a + workflow service does that and verifies the diploma for you. Your + job is three calls: create an exchange, share an interaction URL that points the applicant's wallet at it, then poll until the verified result comes back.

          @@ -20,7 +22,7 @@ permalink: /education/build/verifier/

          Recommended

          Drive a workflow service — don't build the protocol

          - The protocol mess — VCALM, OID4VP, replay protection, and interoperating + The protocol mess — VCALM delivery, OID4VP, replay protection, and interoperating with whatever wallet an applicant brings — is handled by a workflow service. Digital Bazaar's @bedrock/vc-delivery diff --git a/src/verticals/student-id/build/issuer.njk b/src/verticals/student-id/build/issuer.njk index a352c36..8933ec1 100644 --- a/src/verticals/student-id/build/issuer.njk +++ b/src/verticals/student-id/build/issuer.njk @@ -9,9 +9,10 @@ permalink: /student-id/build/issuer/

          Build an issuer

          - You run the school side. To issue a student ID you don't speak VCALM on the - wire yourself — you drive a workflow service that does. As a - coordinator, your whole job is three calls: create an + You run the school side. As a coordinator you speak VCALM — + just the coordinator side of it. You don't run VCALM delivery, the + on-the-wire exchange with the student's wallet; a workflow service + does that. Your whole job is three calls: create an exchange, share an interaction URL that points the student's wallet at it, then poll the exchange until the wallet has been issued the student ID.

          @@ -20,7 +21,7 @@ permalink: /student-id/build/issuer/

          Recommended

          Drive a workflow service — don't build the protocol

          - The protocol mess — VCALM, OID4VCI, OID4VP, and interoperating with + The protocol mess — VCALM delivery, OID4VCI, OID4VP, and interoperating with whatever wallet a student brings — is handled by a workflow service. Digital Bazaar's @bedrock/vc-delivery diff --git a/src/verticals/student-id/build/verifier.njk b/src/verticals/student-id/build/verifier.njk index 691f17f..5885733 100644 --- a/src/verticals/student-id/build/verifier.njk +++ b/src/verticals/student-id/build/verifier.njk @@ -10,9 +10,11 @@ permalink: /student-id/build/verifier/

          Build a verifier

          - You run the relying-party side — a library, a lab door, a partner venue. You - don't speak VCALM on the wire yourself; a workflow service does that - and verifies the proof for you. As a coordinator, your job + You run the relying-party side — a library, a lab door, a partner venue. As + a coordinator you speak VCALM — just the coordinator side of + it. You don't run VCALM delivery, the on-the-wire exchange with the + student's wallet; a workflow service does that and verifies the + proof for you. Your job is three calls: create an exchange, share an interaction URL that points the student's wallet at it, then poll until the verified result comes back.

          @@ -21,7 +23,7 @@ permalink: /student-id/build/verifier/

          Recommended

          Drive a workflow service — don't build the protocol

          - The protocol mess — VCALM, OID4VP, replay protection, and interoperating + The protocol mess — VCALM delivery, OID4VP, replay protection, and interoperating with whatever wallet a student brings — is handled by a workflow service. Digital Bazaar's @bedrock/vc-delivery diff --git a/src/verticals/supply-chain/build/issuer.njk b/src/verticals/supply-chain/build/issuer.njk index e457707..e9e4b0d 100644 --- a/src/verticals/supply-chain/build/issuer.njk +++ b/src/verticals/supply-chain/build/issuer.njk @@ -9,9 +9,10 @@ permalink: /supply-chain/build/issuer/

          Build an issuer

          - You run the brand side. To issue an authenticity credential you don't speak - VCALM on the wire yourself — you drive a workflow service that does. - As a coordinator, your whole job is three calls: create an + You run the brand side. As a coordinator you speak VCALM — + just the coordinator side of it. You don't run VCALM delivery, the + on-the-wire exchange with the product's wallet; a workflow service + does that. Your whole job is three calls: create an exchange, share an interaction URL that points the product's wallet at it, then poll the exchange until the credential has been issued.

          @@ -20,7 +21,7 @@ permalink: /supply-chain/build/issuer/

          Recommended

          Drive a workflow service — don't build the protocol

          - The protocol mess — VCALM, OID4VCI, OID4VP, and interoperating with + The protocol mess — VCALM delivery, OID4VCI, OID4VP, and interoperating with whatever wallet a product or owner brings — is handled by a workflow service. Digital Bazaar's @bedrock/vc-delivery diff --git a/src/verticals/supply-chain/build/verifier.njk b/src/verticals/supply-chain/build/verifier.njk index af1fe29..b80d486 100644 --- a/src/verticals/supply-chain/build/verifier.njk +++ b/src/verticals/supply-chain/build/verifier.njk @@ -9,9 +9,11 @@ permalink: /supply-chain/build/verifier/

          Build a verifier

          - You run the buyer or retailer side. You don't speak VCALM on the wire - yourself; a workflow service does that and verifies the credential - for you. As a coordinator, your job is three calls: create + You run the buyer or retailer side. As a coordinator you + speak VCALM — just the coordinator side of it. You don't run VCALM + delivery, the on-the-wire exchange with the product's wallet; a + workflow service does that and verifies the credential + for you. Your job is three calls: create an exchange, share an interaction URL that points the product's wallet at it, then poll until the verified result comes back.

          @@ -20,7 +22,7 @@ permalink: /supply-chain/build/verifier/

          Recommended

          Drive a workflow service — don't build the protocol

          - The protocol mess — VCALM, OID4VP, replay protection, and interoperating + The protocol mess — VCALM delivery, OID4VP, replay protection, and interoperating with whatever wallet a product or owner brings — is handled by a workflow service. Digital Bazaar's @bedrock/vc-delivery From 22a11211c0b0bcbb437fa283f4e0e72c81781089 Mon Sep 17 00:00:00 2001 From: Derek Scruggs Date: Mon, 8 Jun 2026 12:55:15 -0500 Subject: [PATCH 4/4] Reword "buy off the shelf" to "use off the shelf". Co-Authored-By: Claude Opus 4.8 --- src/verticals/education/build/issuer.njk | 2 +- src/verticals/education/build/verifier.njk | 2 +- src/verticals/student-id/build/issuer.njk | 2 +- src/verticals/student-id/build/verifier.njk | 2 +- src/verticals/supply-chain/build/issuer.njk | 2 +- src/verticals/supply-chain/build/verifier.njk | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/verticals/education/build/issuer.njk b/src/verticals/education/build/issuer.njk index 3d540d8..841c132 100644 --- a/src/verticals/education/build/issuer.njk +++ b/src/verticals/education/build/issuer.njk @@ -28,7 +28,7 @@ permalink: /education/build/issuer/ is one. The workflow service runs that library; you don't.

          - A goal of VCALM is to let you buy a workflow service off the shelf. An + A goal of VCALM is to let you use a workflow service off the shelf. An administrator loads the workflows you need (a reusable issuance config — what to issue, what to ask for). From then on, whenever a graduate action requires a credential exchange, you create an exchange against that diff --git a/src/verticals/education/build/verifier.njk b/src/verticals/education/build/verifier.njk index 4ac9f81..3bdf61c 100644 --- a/src/verticals/education/build/verifier.njk +++ b/src/verticals/education/build/verifier.njk @@ -29,7 +29,7 @@ permalink: /education/build/verifier/ is one. The workflow service runs that library; you don't.

          - A goal of VCALM is to let you buy a workflow service off the shelf. An + A goal of VCALM is to let you use a workflow service off the shelf. An administrator loads a workflow (a reusable verification config — what to request, which institutions to trust, what counts as valid). From then on, whenever an applicant action needs a check, you create an exchange diff --git a/src/verticals/student-id/build/issuer.njk b/src/verticals/student-id/build/issuer.njk index 8933ec1..095abdc 100644 --- a/src/verticals/student-id/build/issuer.njk +++ b/src/verticals/student-id/build/issuer.njk @@ -28,7 +28,7 @@ permalink: /student-id/build/issuer/ is one. The workflow service runs that library; you don't.

          - A goal of VCALM is to let you buy a workflow service off the shelf. An + A goal of VCALM is to let you use a workflow service off the shelf. An administrator loads the workflows you need (a reusable issuance config — what to issue, what to ask for). From then on, whenever a student action requires a credential exchange, you just create an exchange against diff --git a/src/verticals/student-id/build/verifier.njk b/src/verticals/student-id/build/verifier.njk index 5885733..4041a62 100644 --- a/src/verticals/student-id/build/verifier.njk +++ b/src/verticals/student-id/build/verifier.njk @@ -30,7 +30,7 @@ permalink: /student-id/build/verifier/ is one. The workflow service runs that library; you don't.

          - A goal of VCALM is to let you buy a workflow service off the shelf. An + A goal of VCALM is to let you use a workflow service off the shelf. An administrator loads a workflow (a reusable verification config — what to request, which schools to trust, what counts as valid). From then on, whenever a student action needs a check, you create an exchange and diff --git a/src/verticals/supply-chain/build/issuer.njk b/src/verticals/supply-chain/build/issuer.njk index e9e4b0d..92c64b8 100644 --- a/src/verticals/supply-chain/build/issuer.njk +++ b/src/verticals/supply-chain/build/issuer.njk @@ -28,7 +28,7 @@ permalink: /supply-chain/build/issuer/ is one. The workflow service runs that library; you don't.

          - A goal of VCALM is to let you buy a workflow service off the shelf. An + A goal of VCALM is to let you use a workflow service off the shelf. An administrator loads the workflows you need (a reusable issuance config — what to issue, what to ask for). From then on, whenever a product action requires a credential exchange, you create an exchange against that diff --git a/src/verticals/supply-chain/build/verifier.njk b/src/verticals/supply-chain/build/verifier.njk index b80d486..5f96fb3 100644 --- a/src/verticals/supply-chain/build/verifier.njk +++ b/src/verticals/supply-chain/build/verifier.njk @@ -29,7 +29,7 @@ permalink: /supply-chain/build/verifier/ is one. The workflow service runs that library; you don't.

          - A goal of VCALM is to let you buy a workflow service off the shelf. An + A goal of VCALM is to let you use a workflow service off the shelf. An administrator loads a workflow (a reusable verification config — what to request, which brands to trust, what counts as valid). From then on, whenever a sale or check needs it, you create an exchange and read back