A verifier is a regulated provider with a signing key registered by Writ's governance. It checks identity, issues credentials with the holder's consent, hands the holder their credential file, and revokes credentials when it has to.
The provider contract#
Writ starts a hosted session with your service and you send the holder back with a credential.
1. Sessions#
Writ calls:
POST {VERIFIER_API_URL}/sessions
Authorization: Bearer {VERIFIER_API_KEY}
Idempotency-Key: {requestId}
Content-Type: application/json
{ "requestId": "…", "wallet": "…", "returnUrl": "…", "callbackUrl": "…", "programId": "…", "genesisHash": "…" }
Answer with { "id": "<your session id>", "url": "<your hosted page>" }. The hosted page must be on the HTTPS origin configured as VERIFIER_HOSTED_ORIGIN. Writ sends the holder there.
2. Issuance#
On your hosted page, after your checks:
-
Generate four salts uniformly in the BN254 scalar field with a secure random source.
-
Read the wallet's credential account. Its epoch is your expected epoch, or 0 when it has none; the new credential gets the next epoch.
-
Compute the four commitments for the new epoch with the SDK's
computeAttributeCommitments(wallet, verifier, epoch, { values, salts }). -
Build
issue_credentialwith the SDK'sissueCredentialInstruction: the commitments, the credential expiry and the four attribute expiries. The wallet is the fee payer. -
Have the holder's wallet sign first. That signature is their consent. Then co-sign with your verifier key and submit.
-
Give the holder their credential file,
{ "values": [...], "salts": [...] }, every entry a decimal string, over your own authenticated channel. Keep no copy in Writ.
Values are the region code, the status level, the attested minimum age and the screening time in Unix seconds. Use the shared region and status codes.
3. Callbacks#
After the transaction finalizes, call Writ:
POST {callbackUrl}
Content-Type: application/json
x-writ-timestamp: 1791245000
x-writ-signature: v1=<hex HMAC-SHA256(secret, timestamp + "." + rawBody)>
{ "eventId": "…", "type": "credential.issued", "wallet": "…", "signature": "…", "sessionId": "…" }
typeiscredential.issued(with yoursessionId) orcredential.revoked.- The timestamp is Unix seconds and must be within five minutes of Writ's clock.
- Retries reuse the same
eventIdand body with a fresh timestamp and signature.
Writ records the callback at once and acts on it only when its worker finds your finalized transaction, signed by your registered key, with the matching instruction. A callback speeds up Writ's index; it never creates a credential by itself. Identical retries are recognized by their event ID.
Revoking#
Revoke from the Network page with your verifier wallet: look up the holder's wallet and Sign and revoke. Revocation moves the credential's epoch, and every pass from it stops counting at the next transfer. You can also build revoke_credential yourself; it takes the credential's current epoch as a guard.