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_secretas 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
jtifor a later acknowledgement attempt. - Preserve the predecessor until M7 confirms
token_state: "active".