Attestra

The attestation

The signed object every verify-family endpoint returns, field by field.

The attestation is the signed statement Attestra returns from /v1/status, /v1/verify and /v1/verify-quorum. It asserts that at a stated time, Attestra read a stated fact from a stated chain.

The object

{
  "attestation": {
    "DID": "did:eid:4tARKz6hK5GuWr9fkdzsG1Bf9X3b3agzh7RPBdjdpLWapHRh",
    "CredentialID": "0xfb898d4079eed14603bbca2a245278b45af1d2acad278c6c690e067bc9eaae2c",
    "CredType": "KYCVerified",
    "Issuer": "0xe81fa0ecbf9c77cd095fbeb4687d78cbd98bd51092a2cc12d4276e1ade4a8e78",
    "Status": "valid",
    "SourceChain": "eid",
    "IssuedAt": 1751360400,
    "ExpiresAt": 1751360700,
    "Nonce": [0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255, 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255]
  },
  "signature": "0xf4793f2e134d1dcc4884967bba64ef22433cbb63006c4c1aeefc580e151f82ec3c266201648bbf018c32136f7d2c44ab4b666c54e20f22d4cc075ce66d2d2c8e00"
}

Why the keys are PascalCase

This will look like a mistake the first time you see it. It is not: the Go struct behind attestation carries no JSON tags, so encoding/json marshals it with the field names exactly as declared in the source, PascalCase and all. Nonce is a fixed [32]byte array, and Go renders an array of bytes as 32 numbers, not as base64 (that encoding applies only to byte slices) and not as hex.

The contrast is deliberate. The /v1/attestation read-back endpoint returns a different, tagged type, so its keys are lowercase (did, credentialId, nonce, and so on) and its nonce is a hex string, not a number array. A client that calls both /v1/verify (or /v1/status) and /v1/attestation needs to handle both shapes; one is not a typo of the other.

Fields

FieldTypeWhat it is
DIDstringThe subject. Empty on /v1/status, which names no subject.
CredentialIDstringThe source chain's own key for the credential. On eIDChain, the claim hash.
CredTypestringSchema or type identifier. Provenance differs by endpoint, see below.
IssuerstringThe authoritative on-chain issuer.
Statusstringvalid or revoked.
SourceChainstringThe chain the fact was read from.
IssuedAtnumberUnix seconds.
ExpiresAtnumberUnix seconds.
Noncenumber[32]32 bytes, one number per byte.

CredType is not the same fact on every endpoint

On /v1/verify, CredType is the CType hash, 0x-hex, read from chain. On /v1/status, CredType is whatever the caller passed in ?type=, echoed straight into the field and signed unvalidated; it is empty if the parameter was omitted.

State the consequence plainly: on a /v1/status response, CredType is an assertion by the caller, not a fact the oracle checked. It sits inside the signature, which makes it look authoritative, and it is not.

The binding invariant

At least one of DID or CredentialID must identify the subject. Without one, the signed statement is unbound: it asserts that some credential is valid without saying which, so any holder could replay another holder's attestation. This is why /v1/verify, which names the subject, is preferred over /v1/status, which does not, whenever the holder matters.

Freshness

IssuedAt, ExpiresAt and Nonce are all inside the signed digest, so an attestation cannot be re-dated and its nonce cannot be swapped after the fact. Consumers should check ExpiresAt themselves: nothing revokes an attestation early, and Attestra does not track which attestations have already been consumed.

Verifying it yourself

The signature is over a keccak256 digest of these exact nine fields in this exact order, and you can check it yourself without calling Attestra. See The canonical digest for the byte layout, and Verifying an attestation for a worked example.

On this page