Build a verifier
You run the relying-party side — a bar, a dispensary, an employer, an online service. 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 resident'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 resident's wallet at it, then poll until the verified result comes back.
What the coordinator does
- Create an exchange against a workflow that requests only what you need — here, proof of age or proof of a valid license.
- Host and share an interaction URL that points the resident's wallet at that exchange.
- 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. Create the exchange
POST to your workflow's exchanges endpoint. The workflow already
encodes request only "age ≥ 21" and trust the issuing
agency; you pass just the per-exchange variables:
POST https://workflows.example/workflows/age-check/exchanges
{
"ttl": 300,
"variables": {
"reason": "Please prove you are 21 or older to continue."
}
}
This call is authenticated. On success the service returns
204 No Content with a Location header — your
exchange URL:
HTTP/1.1 204 No Content
Location: https://workflows.example/workflows/age-check/exchanges/z19xQ...
Because the workflow asks for a single derived attribute, a well-built wallet
returns proof of "age ≥ 21" — not the resident's name, exact birthdate, or
home address. The workflow's challenge and domain
stop a captured presentation from being replayed.
2. Host and share the interaction URL
The exchange URL is the vcapi interaction endpoint.
Put it in a QR code at the point of sale, or embed it as a link in an
online flow — typically wrapped in a presentation request:
{
"VerifiablePresentation": {
"interact": {
"service": [{
"type": "VerifiableCredentialApiExchangeService",
"serviceEndpoint": "https://workflows.example/workflows/age-check/exchanges/z19xQ..."
}]
}
}
}
The resident's wallet POSTs to that endpoint and runs the exchange with the workflow service directly. You are not in that loop.
3. Poll for the verified result
GET the exchange URL (authenticated) until its state is
complete:
GET https://workflows.example/workflows/age-check/exchanges/z19xQ...
{
"exchange": {
"id": "z19xQ...",
"state": "complete",
"variables": {
"results": {
"ageCheck": {
"did": "did:example:resident456",
"verifiablePresentation": { "...": "verified: ageAtLeast21 = true" }
}
}
}
}
}
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.
You received one derived fact — not the resident's ID.
License check variant
The same three-call pattern works for checking professional licenses. Configure a workflow that requests proof of a valid license class and trusts the relevant licensing board:
{
"ttl": 300,
"variables": {
"reason": "Please prove you hold a current ambulance driver certificate."
}
}
A complete exchange confirms the license is current, not
expired, and signed by a trusted authority — without a real-time lookup to
the licensing board's registry.
That's it
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, 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.