Token acknowledgement

/token/ack is an M7 extension for an acknowledgement-mode refresh package. It is not part of base OAuth 2.0. Most integrations should omit refresh_mode and never call this endpoint.

Use this endpoint only when a refresh request explicitly selected ack or ack_supersede_pending and returned a package whose token_state is pending.

Why acknowledgement exists

An acknowledgement-mode refresh lets an application durably store a proposed replacement before the old credential is retired. It is intended for clients that can safely recover from a process crash or network ambiguity during rotation.

The pending response contains an activation envelope:

{
  "token_state": "pending",
  "activation": {
    "id": "ACTIVATION_UUID",
    "secret": "ACTIVATION_SECRET"
  },
  "predecessor_bundle": {
    "refresh_token": "PREVIOUS_REFRESH_TOKEN",
    "binding_chain": "PREVIOUS_BINDING_CHAIN",
    "binding_link": "PREVIOUS_BINDING_LINK"
  }
}

Persist the entire pending response, including its predecessor bundle, before attempting acknowledgement. Do not use the pending replacement package before M7 confirms activation.

Request

POST https://sso.user.m7.org/token/ack
Content-Type: application/json

The JSON object must contain only these fields:

Field Required Description
activation_id Yes UUID from activation.id. UUID text is case-insensitive.
activation_secret Yes One-time secret from activation.secret.

Example:

curl --fail-with-body --silent --show-error \
  --request POST 'https://sso.user.m7.org/token/ack' \
  --header 'Content-Type: application/json' \
  --data '{
    "activation_id": "ACTIVATION_UUID",
    "activation_secret": "ACTIVATION_SECRET"
  }'

The activation secret itself authorizes acknowledgement for an unbound lineage. Do not add a bearer access token to this request. Do not add application-defined fields: M7 rejects any field other than the two fields above.

An already provisioned DPoP-bound lineage can require a matching proof for ACK. Preserve its private key and follow its provisioned compatibility profile. This is not a general DPoP integration contract; see DPoP status.

Success and commit sequence

M7 returns:

{
  "token_state": "active",
  "activation_id": "ACTIVATION_UUID",
  "idempotent": false
}

Only after this response should your application atomically promote the new package to active storage, delete activation.secret, and remove the stored predecessor bundle. idempotent: true means the same activation had already been completed successfully.

Failure and recovery

If the request times out or fails at the transport layer, retain the full pending envelope and predecessor bundle. For an already provisioned DPoP-bound lineage, a proof error is also ambiguous: retain the state and use a new proof under that lineage's compatibility profile rather than reusing a prior proof identifier.

Some lifecycle errors are terminal:

Error Meaning
activation_expired The pending activation expired.
activation_not_current, activation_parent_not_current, or activation_parent_invalid The package was superseded or its predecessor is no longer valid.
activation_not_pending The activation is no longer pending.

ack_supersede_pending is a controlled recovery mode for an integration that still has the predecessor bundle after an interrupted pending attempt. Do not adopt it without a tested durable-storage and recovery design.

Security requirements

  • Treat activation_secret as a bearer credential at least as sensitive as a refresh token.
  • Do not write activation data to logs, browser storage, analytics, or support tickets.
  • For an already provisioned DPoP-bound lineage, never reuse a proof or jti for a later acknowledgement attempt.
  • Preserve the predecessor until M7 confirms token_state: "active".