DID documents
A verifier resolves two DID documents to check a receipt: the company document
for companySig, and the citizen document for the WebAuthn assertion. They
use different DID methods and expose keys differently.
The company DID document
Resolved from did:web:app.agreely.ca:c:{slug} at
https://app.agreely.ca/c/{slug}/did.json. It resolves only when the company has a
verified domain and an active signing key.
Until August 22, 2026, the company DID host was the apex:
did:web:agreely.ca:c:{slug}. An artifact freezes the DID it was signed under, so a
receipt issued before that date keeps that old DID in its issuer, and Agreely
still recognizes it when it provides the verification material for that receipt.
The address https://agreely.ca/c/{slug}/did.json no longer serves the document,
however: read it at the current address above.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/data-integrity/v2"
],
"id": "did:web:app.agreely.ca:c:acme",
"alsoKnownAs": ["did:web:acme.com"],
"verificationMethod": [
{
"id": "did:web:app.agreely.ca:c:acme#kms-1",
"type": "Multikey",
"controller": "did:web:app.agreely.ca:c:acme",
"publicKeyMultibase": "z6Mk...base58btc..."
}
],
"assertionMethod": ["did:web:app.agreely.ca:c:acme#kms-1"],
"authentication": ["did:web:app.agreely.ca:c:acme#kms-1"]
}
| Field | Meaning |
|---|---|
id |
The Agreely-hosted company DID. |
alsoKnownAs |
The company's verified real domain DID, the binding that proves the Agreely-hosted id belongs to the real organization. |
verificationMethod[].type |
Multikey. |
verificationMethod[].publicKeyMultibase |
The Ed25519 public key as z-prefixed base58btc of `(ed25519-pub multicodec 0xed01 |
assertionMethod / authentication |
Reference the signing key fragment. |
The root passkey and delegation (additive)
Once a company has registered a root passkey and signed a delegation, the same document additively publishes two more elements. A company with no root or delegation produces exactly the document above.
{
"verificationMethod": [
{ "id": "did:web:app.agreely.ca:c:acme#kms-1", "type": "Multikey", "controller": "did:web:app.agreely.ca:c:acme", "publicKeyMultibase": "z6Mk..." },
{
"id": "did:web:app.agreely.ca:c:acme#root-1",
"type": "WebAuthnAuthenticationKey2024",
"controller": "did:web:app.agreely.ca:c:acme",
"publicKeyCose": "0xa5010203262001...",
"rpId": "agreely.ca"
}
],
"capabilityDelegation": ["#root-1"],
"delegation": {
"type": "agreely.keyDelegation.v1",
"body": { "type": "agreely.keyDelegation.v1", "companyDid": "did:web:app.agreely.ca:c:acme", "delegatee": { "keyId": "0x..." }, "rootKeyId": "0x...", "registry": "0x...", "chainId": "8453", "rpId": "agreely.ca", "nonce": "0x...", "validFrom": "2026-07-20T15:00:00Z", "validUntil": "2027-07-20T15:00:00Z" },
"proof": {
"type": "WebAuthnDelegationProof2026",
"verificationMethod": "did:web:app.agreely.ca:c:acme#root-1",
"authenticatorData": "<base64url>",
"clientDataJSON": "<base64url>",
"signature": "<base64url>"
}
}
}
| Field | Meaning |
|---|---|
#root-1 |
The company's root passkey, under capabilityDelegation (the W3C-correct relationship: "the root delegates a capability to a subordinate key"). Its publicKeyCose is the raw COSE public key as 0x-hex; its keccak thumbprint must equal the company's DNS root=. |
capabilityDelegation |
References #root-1: the root's only capability is to delegate, never to sign a consent or authenticate. |
delegation.body |
The exact number-free canonical body the WebAuthn challenge was hashed over (challenge = keccak(JCS(body))), so a verifier recomputes the challenge itself. |
delegation.proof |
The documented proprietary proof type WebAuthnDelegationProof2026: the raw WebAuthn assertion artifacts (base64url) verified against #root-1's COSE key. There is no ratified WebAuthn Data Integrity cryptosuite, hence a published verification recipe. |
An enrolled recovery key, if any, additionally appears as Ed25519VerificationKey2020
under capabilityInvocation (authorized only to rotate the root).
A company that left Agreely
When an organization leaves Agreely and its data is destroyed, its did.json
address keeps answering: it serves, frozen, the last document the route published,
marked "deactivated": true with the date deactivatedOn. Receipts issued before
the departure therefore still verify against the public key they name. An
organization that never had a resolvable document stays 404.
https://app.agreely.ca/c/{slug}/departure.json then serves the public record of
the departure: did, status (purged), sealedOn and purgedOn (dates), and
statementSha256, the digest of the departure statement. Before the destruction,
while the departure can still be cancelled, that address answers 404.
The citizen DID document
Resolved from did:agreely:citizen:... at https://my.agreely.ca/did/{did}. It is public, unauthenticated, and
tenant-less by design (any verifier may resolve it). It is assembled from the
full key history, so every key the DID ever held appears with its authority
window.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6G",
"verificationMethod": [
{
"id": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6G#passkey-1",
"controller": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6G",
"validFrom": "2026-01-10T14:00:00Z",
"validUntil": null,
"type": "WebAuthnAuthenticationKey2024",
"publicKeyCose": "0xa5010203262001..."
},
{
"id": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6G#recovery-1",
"controller": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6G",
"validFrom": "2026-01-10T14:00:00Z",
"validUntil": null,
"type": "Ed25519VerificationKey2020",
"publicKeyMultibase": "z6Mk...base58btc..."
}
],
"authentication": [
"did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6G#passkey-1"
],
"capabilityInvocation": [
"did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6G#recovery-1"
]
}
| Field | Meaning |
|---|---|
validFrom / validUntil |
The key's authority window. validUntil is present even when null so a verifier distinguishes "still valid" from "expired at T". A receipt signed at time T resolves to whichever key was authoritative at T. |
WebAuthnAuthenticationKey2024 |
A passkey. Its publicKeyCose is the COSE-encoded public key as 0x-hex; verify the receipt's WebAuthn assertion against it. |
Ed25519VerificationKey2020 |
A recovery key, exposed as publicKeyMultibase. |
authentication |
Passkey fragments (authorized to sign consent). |
capabilityInvocation |
Recovery-key fragments, authorized solely for DID rotation, never for signing consent. |
The window is what keeps old receipts valid
When you verify proof[1] of a receipt, take its verificationMethod fragment, find that method in the citizen document, and confirm its [validFrom, validUntil] window contains the receipt's grantedAt. Then verify the assertion against that method's COSE key. A rotated-out key still verifies its historical receipts.
Next
- Verify a receipt ties both documents into the four-check recipe.