Skip to main content

Verify attestation

Validates an XitAttestationToken produced by the device.

POST /api/v1/attestation/verify

Authentication

Requires a valid API key in the X-Api-Key header. See Authentication.

Request

The body is JSON. Byte-array fields are base64-encoded.

FieldTypePresenceDescription
attestationTokenstring (base64)requiredThe CBOR-encoded XitAttestationToken produced by the device.
sessionReferencestringconditionalSession identifier returned by POST /attestation/init. Required in the session approach; must be absent in the sessionless approach.
deviceNoncestring (base64)conditionalDevice-nonce part. Optional in the session approach (only when the integration uses a device nonce). Must be absent in the sessionless approach.
expectedNoncestring (base64)conditionalThe full final nonce. Required in the sessionless approach; must be absent in the session approach.

Exactly one of sessionReference or expectedNonce must be present. Sending both, or neither, is rejected as a malformed request.

Variant: session approach

Sample request — session
curl https://attestation.example.com/api/v1/attestation/verify \
--request POST \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: xma_SampleKey' \
--data '{
"attestationToken": "[base64 CBOR token]",
"sessionReference": "019dd9b7-6c0f-755d-be98-2e82a6d067a0",
"deviceNonce": "HB/xSNrFd1A6jobTa13ZohuajtBKkF6ORQQPAFqpd9Y="
}'

deviceNonce is omitted when the integration does not use a device nonce.

Variant: sessionless approach

Sample request — sessionless
curl https://attestation.example.com/api/v1/attestation/verify \
--request POST \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: xma_SampleKey' \
--data '{
"attestationToken": "[base64 CBOR token]",
"expectedNonce": "8wY7WRuvpD8CtNuMuQeJV6nt9ToZxn9JQ2NBD8GEo8A="
}'

Response

Success — 200 OK, isValid: true

Sample success response — Google Play Integrity
{
"isValid": true,
"googleTokenDetails": {
"packageName": "com.example.app",
"deviceIntegrityVerdicts": ["MEETS_DEVICE_INTEGRITY", "MEETS_BASIC_INTEGRITY"],
"timestampMillis": 1714485269871
},
"statusCode": 0
}
Sample success response — Apple App Attest
{
"isValid": true,
"appleTokenDetails": {
"keyIdentifier": "iLJVbLGq4...3uhP/I=",
"appId": "ABCDE12345.com.example.app",
"environment": "Production",
"assertionCounter": 7
},
"statusCode": 0
}
FieldTypeDescription
isValidbooltrue when the token passed all checks.
googleTokenDetailsobjectPresent when device.service is "gms". Omitted otherwise.
googleTokenDetails.packageNamestringPackage name asserted by the Play Integrity token. Compare against the package the RP expects.
googleTokenDetails.deviceIntegrityVerdictsstring[]Verdicts from Play Integrity (e.g. MEETS_DEVICE_INTEGRITY, MEETS_BASIC_INTEGRITY, MEETS_STRONG_INTEGRITY).
googleTokenDetails.timestampMillislongToken timestamp in milliseconds since the Unix epoch.
appleTokenDetailsobjectPresent when device.service is "apple". Omitted otherwise.
appleTokenDetails.keyIdentifierstringApp Attest key ID, base64. Echoed from the input token.
appleTokenDetails.appIdstringThe bound app ID extracted from the attestation (<TeamID>.<bundle-id>). Compare against the app the RP expects.
appleTokenDetails.environmentstring"Development" or "Production".
appleTokenDetails.assertionCounteruintMonotonic per-key assertion counter from App Attest. Validated server-side — the API rejects any token whose counter is not strictly greater than the previous one for the same key.
statusCodeuint0 on success.

The Attestation API enforces app/package binding, environment, and assertion-counter monotonicity against its own configured allowlist. The token-details fields are exposed for telemetry and for any RP-specific policy that goes beyond the API's defaults — for example, requiring MEETS_STRONG_INTEGRITY rather than just MEETS_DEVICE_INTEGRITY for high-value operations.

Token rejected — 200 OK, isValid: false

Sample token-rejected response
{
"isValid": false,
"statusCode": 0
}

The HTTP status is 200 and statusCode is 0 because the call itself completed normally — the token simply did not pass validation. The RP must deny the protected operation.

Reasons for isValid: false include (the response does not disclose which):

  • The attestationToken could not be parsed as a valid XitAttestationToken.
  • In the session approach: the sessionReference does not exist, has already been consumed, or has expired. (Note: a session is consumed even when validation fails, so a subsequent retry with the same sessionReference always returns isValid: false.)
  • The final nonce reconstructed by the API does not match the nonce embedded in the token.
  • Platform-specific validation failed (signature, certificate chain, app binding, integrity verdict, etc.).

Specific failure reasons are recorded in server-side logs.

Error responses

HTTP statusstatusCodeCause
400 Bad Request0x10011001Malformed request: attestationToken missing, neither sessionReference nor expectedNonce provided, or both provided.
401 UnauthorizedX-Api-Key header missing.
403 ForbiddenAPI key invalid, unknown, or revoked.
500 Internal Server Error0x10011000Unexpected failure (database, validator lookup, platform verification crash).
Sample error body
{
"statusCode": 268505089,
"errorMessage": "Either SessionReference or ExpectedNonce must be provided."
}

Status code reference

statusCode (hex)statusCode (decimal)Meaning
0x000000000Success — applies to both isValid: true and isValid: false responses.
0x10011000268505088Internal error.
0x10011001268505089Invalid request.

Authorization decision

The RP grants the protected operation only when both of the following hold:

  • HTTP status is 200.
  • isValid is true.

Optionally, the RP may also enforce its own additional policy on top — for example, requiring a specific integrity verdict — by inspecting the token-details fields. App binding, environment, and counter monotonicity are already enforced by the API and do not need to be re-checked.

Anything else — isValid: false, HTTP 4xx, HTTP 5xx — must result in the RP denying the operation.