Skip to main content

Attestation

Attestation Response Code

An Attestation Response Code (ARC) is a short alphanumeric code that is assigned to all Approov fetch events. The ARC encodes a subset of the device property flags of the device making the request. It also encodes whether a fetch resulted in attestation pass or not. The encoding ensures it is not possible to determine the actual properties without Approov backend access. When fetching Approov tokens, the ARC also embedded inside the received Approov tokens themselves in the arc claim.

ARC provides a way of finding out why an attestation may have failed, and the overall properties of a device, without leaking this information to the end user or, indeed, any attacker. This is because the ARC cannot be decoded without access to the Approov cloud service. It may also be useful for internal debug purposes or internal support.

The ARC can be provided in error information presented to the user or submitted by the app in an API call, for cases where the user is having trouble using their app. The base32 encoding and short length means that it is possible to read out or submit this information reliably for support purposes. The encoding also includes internal checking that detects most transcription errors. This facility might be needed if the app is not usable because invalid Approov tokens are being issued. When the ARC is decoded it is then possible to ascertain why Approov fetches are failing and for remedial action to be taken.

tip

Approov also provides a more direct way of determining why a particular attestation was rejected using Rejection Reasons. This provides the causes in the SDK so that the app can directly inform the user. This feature is off by default.

The ARC will be enabled by default but if necessary you can enable it as follows:

approov policy -setARC on

Changing the ARC policy requires an admin role and confirmation:

WARNING: updating the arc policy will have an immediate impact on your apps in production
ATTENTION: If you wish to continue then please type YES and return: YES
arc policy was set successfully

Although you are able to set the arc policy to off we do not recommend this.

The current status of the ARC policy can be obtained as follows:

approov policy -getARC

This will output the state and a list of the device property flags that can be encoded in the ARC if it is enabled:

ARC policy is on
flags that can be ARC encoded:
app-not-registered
bad-hmac
...

If any of those properties were set for a fetch then you will be able to determine that by decoding the ARC.

info

The codes are encoded using a cryptographic secret so that it is not possible to determine their meaning without access to the Approov account backend. Moreover, the encoding for each Approov account is unique so codes generated by one account cannot be decoded by another.

Decoding an Attestation Response Code

The Attestation Response Code is embedded in the arc claim of all issued tokens. However, if you wish to obtain the code directly then certain Mobile App Quickstarts errors provide it directly. See the quickstart documentation for more details.

An API endpoint is provided that takes an ARC and returns the decoded information for it. This is designed for use in backend customer support systems. Information about how to access the API endpoint is provided as follows:

approov token -showArcInfoCurl

This will output an example curl command such as the following:

curl -H "Authorization: Bearer xxx..." -H "Arc: <arc>" https://<account-domain>/arc-info/

The API key in the Authorization is account specific and should not change unless a specific support request is made to request its rotation. The API key only allows the decoding of tokens and obtaining additional token information and does not enable any other actions on the account. However, as with any account credential, steps should be taken to keep it secured and to prevent accidental disclosure or inclusion in source code repositories.

A curl request should output something like:

{
"status": "failed",
"deviceProperties": [
"app-not-registered",
"rooted",
"root-risk",
"xposed"
]
}

The status indicates if the attestation was passed or failed. If the provided ARC is invalid in some way (perhaps issued to a different Approov account or entered incorrectly) then the status will be arc invalid.

The deviceProperties lists device property flags that have been determined from the device. If the status is failed then this will almost certainly pinpoint the reason(s).

Note that, although the output is a curl command, you can of course use an alternative tool or command to perform the GET request.

warning

This API is for the use of your backend systems only. Never build calls to this API, and especially not the Authorization key required, directly into your mobile app. Moreover, this endpoint should only be used for informational purposes, as it may be subject to rate limiting and does not have the Cloud Server Redundancy features of the Approov token issuance process.

Rejection Reasons

This feature allows an app to directly determine if an Approov operation has failed because the device does not meet the rejection criteria for the current security policy. See the Mobile App Quickstarts documentation to see how it can be obtained for your integration. It is provided as a comma separated list of the device properties that are asserted and are in rejection set for the current rejection policy.

warning

This feature is not enabled by default. If you enable it then the app will be able to determine programmatically whether a particular fetch request has passed or failed, and the reasons for failure. By default, and if the feature is again disabled,an app is unable to determine if a token is valid. This is by design, as it reduces the information available to attackers. Only use this feature if you wish to directly inform the user about failures and their causes.

You can enable this feature as follows:

approov policy -setRejectionReasons on

Which will output something like:

WARNING: updating the rejection reasons policy will have an immediate impact on your apps in production
ATTENTION: If you wish to continue then please type YES and return: YES
rejection reasons visibility policy was set successfully

Changing the rejection reasons policy requires an admin role and confirmation. Use off to disable it again.

The current status of the rejection reasons policy can be obtained as follows:

approov policy -getRejectionReasons

The rejection reasons information may be presented to the user as an explanation of why they are unable to proceed in the app. You can rely on the device property names not changing, so you may wish to split the string and check against individual commonly occurring properties (e.g. rooted or jailbroken) and provide a more detailed explanation to the user.

info

This rejection reasons capability is only available in version 3.0.0 and later SDKs.

ARC flags mapping

The ARC flags mapping is a list of global ARC flags that may be encoded in the arc claim of issued tokens. This is useful for understanding the attestation results after decoding the arc claim. The list can be obtained as follows:

approov policy -getARCFlagMappings <pathname>

This will output the ARC flags mapping to the provided pathname. For example:

{
"1": "debug",
"10": "ios-simulator",
"11": "xposed",
"12": "xposed-unsafe",
"13": "cydia",
"14": "frida",
"15": "cycript",
"16": "cloned",
"17": "risky-device",
"18": "filtered",
"19": "filtered-reject",
....
}

Managing ARC Encoding Secrets

The following features allow you to manage the ARC encoding secret for your account. This can be useful for debugging purposes, for example getting the active ARC encoding secret in use for decoding tokens, or to change the ARC encoding secret if you have any reason to believe it has been compromised. These operations require an admin role. The account uses an active ARC encoding secret to encode the arc claim in issued tokens, and you may stage a pending ARC encoding secret to be promoted to active at a time of your choosing. When a secret is replaced it is added to an archive so that tokens issued with it can still be decoded. The following operations are available.

To get the active ARC encoding secret for the account, which is used to decode the arc claim in issued tokens, along with the pending ARC encoding secret if one is available:

approov secret -arc get

The new operation creates a new pending ARC encoding secret for the account with a random value. It will overwrite any existing pending ARC encoding secret.

approov secret -arc new

The get and new operations output the active ARC encoding secret and the pending ARC encoding secret if available as follows:

Current ARC encoding secret: FSx]_QA?qw@(be,m2K0-U
Pending ARC encoding secret: :Ig~P?#M@vljSdY4D8:Ag

The change operation archives the active ARC encoding secret, promotes the pending ARC encoding secret to become the active ARC encoding secret, and then clears the pending ARC encoding secret. This means that all new tokens issued after the change will be encoded with the new active ARC encoding secret. There must be a pending ARC encoding secret (created with approov secret -arc new) for this operation to succeed, and confirmation is required.

approov secret -arc change
warning

Changing the active ARC encoding secret will have an immediate impact on newly issued tokens. This means that previously issued tokens will only be decodable with the archived ARC encoding secret, and new tokens will only be decodable with the new active ARC encoding secret.

The list operation shows the active and pending ARC encoding secrets, followed by the archived ARC encoding secrets with the timestamp indicating when each was added to the archive. This allows you to decode tokens issued with an earlier ARC encoding secret by referencing the appropriate archived secret.

approov secret -arc list

Example output of the list operation is as follows:

Current ARC encoding secret: FSx]_QA?qw@(be,m2K0-U
Pending ARC encoding secret: :Ig~P?#M@vljSdY4D8:Ag
Archived ARC encoding secrets with the timestamp they were added to the archive:
K2+_]l3k5.%=-3TW*DK|f 2026-04-08 12:19:30 +0100 BST
,?YNXcg(Ow5}f-sug#=F5 2026-03-12 12:12:39 +0000 GMT
LtM#+q{RA@:-5b3m*;g7G 2026-03-10 16:29:41 +0000 GMT

Get backend configuration

Get the current backend configuration for the account, which is written to the provided pathname. This single file contains everything required by an Approov backend integration to verify and decode the tokens issued for your account: the ARC encoding secrets, the default account secrets, the pass and fail key sets, the ARC flag mappings, and the set of API domains along with the key each one uses. For example:

approov secret -getBackendConfig <pathname>

This will output the backend configuration to the file <pathname> with content like the following:

{
"currentArcEncodingSecret": "FSx]_QA?qw@(be,m2K0-U",
"pendingArcEncodingSecret": ":Ig~P?#M@vljSdY4D8:Ag",
"previousArcEncodingSecrets": [
{
"secret": "K2+_]l3k5.%=-3TW*DK|f",
"timestamp": "2026-04-08T12:19:30+01:00"
},
{
"secret": "LtM#+q{RA@:-5b3m*;g7G",
"timestamp": "2026-03-10T16:29:41Z"
}
],
"defaultPassJWKS": {
"keys": [
{
"alg": "A256GCMKW",
"k": "...<default account JWE secret if enabled>...",
"kid": "_A256GCMKW",
"kty": "oct",
"use": "enc"
},
{
"alg": "HS256",
"k": "...<default account JWS secret>...",
"kid": "_HS256",
"kty": "oct",
"use": "sig"
}
]
},
"passJWKS": {
"keys": [
{
"alg": "HS256",
"k": "...<different from fail keyset>...",
"kid": "test-key",
"kty": "oct",
"use": "sig"
}
]
},
"failJWKS": {
"keys": [
{
"alg": "HS256",
"k": "...<different from pass keyset>...",
"kid": "test-key",
"kty": "oct",
"use": "sig"
}
]
},
"arcFlagMappings": {
"1": "debug",
"10": "ios-simulator",
"...": "and the rest"
},
"apis": {
"myapi_keyset.com": {
"kid": "test-key",
"isPassKeyPresent": true,
"isFailKeyPresent": true,
"comment": "API uses key ID present in both pass and fail key sets"
},
"myapi_default.com": {
"kid": "",
"isPassKeyPresent": true,
"isFailKeyPresent": false,
"comment": "API uses default account secret with signed(HS256) form, no fail secret available"
},
"shapes.approov.io": {
"kid": "",
"isPassKeyPresent": false,
"isFailKeyPresent": false,
"comment": "API uses Approov managed consumable API, no exported secrets available"
}
}
}

The individual properties of the backend configuration are as follows:

  • currentArcEncodingSecret, pendingArcEncodingSecret and previousArcEncodingSecrets: The ARC encoding secrets used to decode the arc claim of tokens. The current secret decodes newly issued tokens, the pending secret is provided if one has been staged, and each previous secret is listed with the timestamp it was archived so that tokens issued before recent rotations can still be decoded.
  • defaultPassJWKS: The default account secret, or secrets, presented as a JWKS (explained below). This is the only command that exports the default account secrets in JWKS form.
  • passJWKS and failJWKS: The pass and fail key sets. The passJWKS holds the keys used to sign or encrypt tokens for passing attestations, and the failJWKS holds the corresponding keys for failing attestations. Each entry shares the same kid and algorithm across both key sets but uses different key material, which allows a backend to distinguish genuine passing tokens, genuine failing tokens, and tokens that were not minted by Approov.
  • arcFlagMappings: The ARC flag mappings that translate the numeric flags in a decoded arc claim into device property names (truncated in the example above).
  • apis: The set of account API domains and, for each, the kid in use, whether a pass key (isPassKeyPresent) and a fail key (isFailKeyPresent) are available in the configuration, and a comment explaining the entry. An empty kid indicates that the API uses the default account secret. This map is provided for informational purposes only, showing which secrets were in use by which APIs at the point the configuration was obtained. A backend should not use it to select the verification key, because that assignment can change (for example during a secret rotation). Instead, the backend should trust the kid in the token header and use the corresponding key set entry, verifying the aud claim of the token if there are multiple APIs with different assigned key IDs.

The defaultPassJWKS holds the default account secrets in JWKS form using a custom key ID scheme that reflects the way default account secrets are managed. There is always an HS256 signing (use: sig) entry for the default JWS secret and, only if JWE tokens are enabled for the account, an A256GCMKW encryption (use: enc) entry for the default JWE secret. Specifying a key ID for the default account secrets is optional and is always identical, so to keep the two entries distinct the -getBackendConfig command constructs their kid values by appending a suffix to whatever key ID has been set for the default secret: the HS256 entry appends _HS256 and the A256GCMKW entry appends _A256GCMKW. This means that any backend API accepting the default account secrets should append the appropriate suffix to the key ID it extracts from the token in order to select the correct entry from the configuration. To minimise overhead, your backend API should know whether it is valid to accept the default account secrets or not and it should prefer to find a keyID in the passJWKS before trying the defaultPassJWKS.

It also reports which of the account's API domains are supported by the backend configuration for ARC decoding, as follows:

backend configuration written to file <pathname>
the backend configuration supports ARC decoding from passing tokens for the following APIs:
- myapi_keyset.com (Key ID: test-key)
- myapi_default.com (Key ID: )
the backend configuration supports ARC decoding from failing tokens for the following APIs:
- myapi_keyset.com (Key ID: test-key)
the backend configuration does not support ARC decoding for the following APIs:
- shapes.approov.io (Reason: API uses Approov managed consumable API, no exported secrets available)

Rotating Backend Secrets

When you rotate the secret used to verify tokens for an API, both the old and new secrets are in use for a short period. This is because running apps may still hold cached tokens signed with the old secret (for up to the token lifetime, typically 5 minutes) while newly issued tokens use the new secret. The backend configuration is designed to make this transition safe, since it can contain both the old and new secrets at once.

If you are currently verifying tokens with the default account secret then we recommend migrating to a key set on the first rotation. Using a key set means the key ID (kid) changes with each rotation, so both the old and new secrets can be present in the configuration simultaneously and it is always possible to verify tokens throughout the transition.

The general procedure to rotate the secret for an API is as follows:

  1. Add a new key, with a new kid, to the key set. Download the new backend configuration (which now contains both the new and the old secrets) and deploy it to production. Ensure your backend integration is set to permit any key present in the key set. If you have multiple APIs using different secrets then enable audience inclusion with approov policy -setAudience on and verify the aud claim of the token to select the correct API.
  2. Once the new configuration is live, change the API to issue tokens signed with the new key using approov api -add <domain> -keySetKID <new-kid>. Make sure to also include any other flags that were previously set for the API, as adding it again replaces its configuration.
  3. Wait for the API to stop receiving tokens signed with the old secret. Since most accounts have a token lifetime of 5 minutes, this period should be at least that long.
  4. Remove the old key from the key set (or, if you were rotating the default account secret and nothing else is using it, change the account secret key) so that tokens signed with the old secret will no longer verify.
  5. Finally, download the new backend configuration again and apply it to production so that the old secret is no longer accepted.