Using the server-initiated flow

A service provider (SP) does not always have a user present in a user agent. For example, a user may be calling the SP call center or visiting an SP retail outlet. In such a case, the SP can generate a server-to-server request to initiate the experience on the subscriber's primary phone. For this request, the SP must already have the user's unique pairwise subscriber id.

These assumptions are made:

  • users have the app
  • users have registered for zenkey
  • users have visited this SP and granted consent for at least openid
  • the SP has a record for the users unique pairwise ID
  • the SP has registered for server initiated access
  • the SP has keys (not secret) registered on the portal


All carriers will support the async-token flow to support server-initiated requests. The following variables are available for an SP or service provider SDK to use in constructing the request. Individual carriers may inconsistently support other parameters not defined as part of ZenKey. SPs should pass all attributes inside a request_object.



The SP must make a POST request to the server_initiated_endpoint. All carriers currently have deployed the server_initiated_endpoint as a separate endpoint.

A server-initiated flow must use POST, which will be returned as a 200 instead of a 302 redirect with the code.


content-type: For POST messages, the carrier will support application/json and application/x-www-form-urlencoded. The preference is for SPs to use json.

accept is ignored for server initiated requests

URI attributes

NA. SPs must pass attributes in the body.


Attributes must be passed in the body as detailed below

SP shall not define attribute that conflict between the request object, the post body parameters, or the URI parameters. Carrier responses may be inconsistent when attributes conflict. Generally, carriers should take the request object parameters to supersede other provided attributes as defined in the oidc core spec.

Note: Carriers are inconsistent. A carrier may return an invalid request if the carrier determines attributes inside and outside the request object are different.

Body attributes

<oidc_attributes> including
response_type and client_id

Server initiated should take all attributes from the request object. Clients should not pass outside the request object.


Optional: the SDK may auto enrich this element outside the request object. Carriers only use this for logging at this time.


A JWT built and signed by the SP to pass the request attributes The request JWT is a required attribute for server-initiated calls. The request object must be in a POST body.

Note: Where a Client (SP) has configured multiple keys, the client should use a KID in the header of the JWT to indicate which key has been used.

Note: A request object is optional in the normal device flow. At this time carriers will only support the response_type "code". Therefore, even with a signed request, the SP will again need to sign their token call.

For a server initiated request, the request object must contain all OIDC attributes to conform to OIDC FAPI guidelines.

For either server initiated or regular browser flows, the SP shall include parameters inside both the request object and the regular request body/uri. An error may be returned in the event attributes don't match.

Note: Best practice is that carriers will ignore attributes outside of a request object

Carrier may return invalid request when this is not the case

Request object attributes


kid: Required if the SP has registered more than one key in the SP portal.

kid: Required
kid = Regex=^([a-zA-Z0-9]){1,36}$

Note: Carriers may skip validation of KID. This is an instruction to the SP, as some carriers do validate.


Optional: In the event of a server initiated call, the SP should provide a notification token for use by the carrier when sending the token when ready to the SP.


Required: SP must define where the carrier should send the token when ready. The carrier must verify the notification_uri is a valid one registered to this client_id.


Epoch seconds

SP statement for when the request object was signed. Carrier may have inconsistent response. Generally, carriers should deny requests more than 5 minutes old.


Required: The Client_ID of the SP making the authorize request.

Note: the client assertion JWT requires the ISS and SUB to be present. In order for carriers to maintain consistency and to make a more consistent process for the SP, the SP should provide the ISS, Client_ID, and SUB in all the SP created JWT tokens. The carriers may ignore this value depending on their internal implementations.
Carriers may not return the error that this value is missing. For carriers that require this, it will be an invalid client_assertion error.


Epoch seconds

SP statement for when the request object should expire. Carrier may have inconsistent response. Generally, carriers will not allow a client to set exp longer than 5 minutes.


Optional: SPs should add a jti to help prevent request token reuse or replay attacks.

Carriers should deny any request with a repeated jti within 5 minutes.


Required. ISS of a request jwt must be the client_id for this client.


Required. When present, this should be the ISS URL of the correct carrier.


Required. Number of seconds

format: json integer

Required: When submitting a server-initiated request a client must ask for a time out for how long the push message should be valid for before being canceled (with a timeout)

Note: SPs should not submit a request for more than 48 hrs. Carriers may respond with errors or drop requests that exceed this.


Optional: Method for SP to report version information

Required: To be sent by the SDK if request made by ZenKey SDK.

An SP that builds their own integration is recommended to provide an implementation version so that ZenKey might use it when helping with any debugging.

Carriers will not implement any logical restrictions at this time based on the value the client reports here. Note carriers may add this to the audit logs

Carriers will ignore unrecognized attributes.


The list of scopes being requested for user consent. See Scopes for full details on available scopes.

Launch scopes include name, email, phone, postal_code, openid, offline_access, address, birthdate


Optional. Only select SPs will be allowed to ask for prompt=none, which should always return "user interaction required" when a client does not have silent_authorization_enabled set to true in their SP configuration.

Prompt may now be a space separated list of elements.

  • none
  • consent
  • login

none: For SPs allowed, the user will not be asked to do the click through "sign in"
consent: The user will need to confirm all scopes being requested, even if previously granted.
login: User will be forced to authenticate, even if user's authentication has already happened within the last 30 minutes.

Note: Prompt login used in combination with acr_values=a1 will trigger user authentication. SPs are recommended against this combination. (Carrier may be inconsistent until a POST GA date.) Token issued will be a1

Note: Prompt login may be implemented within a carrier internal API to be met when a subscribers auth_time is less than 60 seconds, to avoid logical loops.


Optional space-separated list of options

  • light
  • dark
  • local_redirect

light & dark: An SP can ask for a dark mode experience for the user; for example, if the SP is using DARK ui. If light or dark are not requested, ZenKey app will use system default. Carrier overwritten UI will be informed of this state. Any request with both light and dark will have this attribute ignored.

local_redirect: Optional. Any push-initiated request should open the redirect on the local phone in addition to closing the normal push request. The authcode and state will be returned to the original requestor on the redirect uri. The local opened redirect will contain the client defined state but not the auth code. It is up to the SP to connect the two device sessions together.

Example: On a TV client, an SP submits a server initiated authorization request with options=local_redirect and with a state=foo. The SP server will receive the POST back for the access_token and id_token, while the TV app on the phone will launch with the universal link redirect_uri with the state=foo.


Not supported at this time


async_token. This must be set for a server_initiated request.


The SP-registered client_id


Required. A https (or custom) scheme. No wildcarding is supported, so this exact URI must be configured in your SP portal.

Encoded with Json escape, as it is inside the json JWT request object.


Optional. Any SP-provide value; this will be returned with the auth_code


Optional. Any SP-provided value; this will be included in the ID_token


Optional: one of

  • a1
  • a3

SPs should ask for a1 when they need a low level of authentication, users will not be asked for their pin or biometrics. Any user holding the device will be able to authenticate/authorize.

Note to carriers: A ccid_token with an a1 ACR should result in 40x errors from the internal consents, and internal history list services, and profile edit services (edit email, address, add recovery codes, enable bio...). a simple profile read should be allowed with an a1 ccid_token. A user launching the ZenKey app triggers these API calls, which will trigger the user to elevate to a3. In contrast, SP consent flows alone do not trigger these APIs, so a1 flows will work.

SPs should not use "a2" as carrier behavior will be inconsistent

Unrecognized acr_values will be rejected as an invalid request


Optional. SPs performing discovery may get a login_hint_token from the discovery_ui endpoint. When provided by discovery it should be used. The login_hint_token will be a JWE (encrypted) and is only readable by the specific carrier. The carrier will be able to extract from the JWE at least the following.

  • Sub: The unique carrier Subscriber ID
  • Bid: The unique browser ID the JV has assigned this browser.
  • Friendly name: JV assigned device labels for this unique browser "chrome browser on windows NT" or "mom's laptop"
  • Iat: login_hint_token was issued at timestamp in seconds since epoch.

Login_hint_token should be only used in secondary device flows.

Some server-initiated flows like for 10' devices (such as a TV application which cannot open a browser) may use the server-initiated flow with a login_hint_token.


Optional. An SP may use a clear text login_hint when the SP is requesting a specific user to be authenticated or authorize.

Value will be one of the following:
• Phone_number: in the form found in userinfo (+1xxxxxxxxxx)
o Post GA: and only when SP has “enum_enabled”.
• sub(pairwise) in the form found in userinfo
An SP will only be able to use phone_number if the client_id has “enum_enabled” in the SP configuration.
This parameter login_hint ( or login_hint_token) is required for server-initiated calls.

An SP should never supply both a login_hint and login_hint_token. Behavior from the carriers will be inconsistent. Generally, carriers will take login_hint_token with precedence and ignore any additional login_hint.
An SP should avoid the use of login_hint on the primary or secondary device flows. Carriers may have inconsistent behavior if used. Generally, carriers will return an invalid_login_hint error if the login_hint is for a different user than the current device.


Optional: SPs will be able to submit "text string" for authorization by the user. Best practice is a server-initiated request should contain a context parameter, so that a user understands the reason for the interaction. A context string may be present in device, secondary, or server-initiated flows.

Note: An SP should use minimal scopes when providing a "context" as the user will have to approve any unconsented scopes before approving the "context".

Maximum decoded size will be <240> characters. Any request with a context that is too large will result in an OIDC error. (invalid request).

Regex= ^[a-zA-Z0-9-%]{8,240}$
The intention is that the SP can submit context strings with any of the following special characters "%" "$" "#' "@" "!" "&" "?" "(" ")". "{[}]|<>,./^

Encoding Plan:

  • Inbound to ZenKey (url-encoded)
  • inbound to SI request object (json escaped (as it's in the request object))
  • inbound to 2ndary device flow (url-encoded for uri.., www-formencoded)
  • ZenKey to carrier SDK (clear)
  • carrier SDK to carrier server ( carrier choice)
  • carrier SDK to RP in challenge (clear)
  • carrier SDK to RP in the case of a push ( carrier choice)
  • carrier out in id_token (json escaped)


SPs may send a tracking ID used for transaction logging. SPs will need to use the service portal for access to any individual log entries.

Regex= ^[a-zA-Z0-9-_]{0,128}$
The intention is that the SP can submit correlationid strings with any of the following special characters "" or "-"



When provided inside the body or request object the context would be clear text. json escaped



A normal access_token will be issued to the SP with the SP's public Key.

When an SP wishes the token to be issued to their client, then the SP can insert the SP client's public key to this field, and the Access_token will be issued to this referred_binding instead of the SP's public key (the one used to verify this request object).

Note: a referred binding must be a jwk format

Note: this is a post GA 1.2 feature

Example of a decoded login_hint_token. Carriers are the only parties able to decrypt the login_hint_token to access this information

"friendly_name":"Chrome on iMac",
"dpop":"<JWT from JV.... To prove issuer>",
"location":"Dallas TX"

example signature dpop from the login_hint_token

}{signed by JV}


sample server-initated flow POST

POST /authorize HTTP/1.1
Content-Type: application/json


sample version of form url encoded. (not currently supported).

POST /authorize HTTP/1.1
Content-Type: .... 


Sample decoded server-initiated request object:

  "alg": "HS256",
  "typ": "JWT"
"iss": "ccid-client001",
"sub": "ccid-client001",
"aud": "",
"iat": 1626909404,
"exp": 1626909704,
"response_type": "async-token",
"client_id": "ccid-client001",
"redirect_uri": "",
"scope": "openid name phone email picture",
"state": "1",
    "kty": "RSA",
    "n": "wgw6hk4gWMqAfU-R2qMOYHDwSCIpbS1cjOL6PE8YmtXM4K5Nlxh_beHbFq3FyF40GnzW_zynpV2XUVN_ryCAGmQmdALOS7ZauQ_d_ijvTxSxpB8VothuN52kfYTp6ypYjIznfiUVHQKXWPHP8GCWw8RhrsO1vXtzLCwARRCvk5z01SbTvCMe_Byrh0aKFn1OaYe3JX03srp3TdgTb_JjumUgYgQJOrTfBcKZY7Pemf9x5zELrV9fwBZDJ_YfhNBFeAoCz77Uws386FjZSIlxpolVFR6zNcJ2vY0_48m_2qMZLYZAF2_jTcglv2Z8auOTs8tu5GXMP1_FkNEDJqYH1w",
    "e": "AQAB"}

Note: items like login_hint may also appear in the request object like any other of the authentication parameters


If the correct response type is used (async_token) and the request contains a valid request object JWT signed by the SP , and the request is made to the carriers defined server-initiated-endpoint. Then the response will take the following form. on an HTTP 200.


Confirmation of the request id from the auth request.



the carrier will generate a request id to identify the now pending/processing authorization request being sent to the subscribers phone

ZenKey will not support polling at this time.


Regex=^ccid-([ 0-9]){2,10}$
Number of Seconds till the request will expire.
If the user has not responded within this time frame the request will be timed out and canceled.

Note: IF an SP has Requested a 7-day request. the carrier may respond with the request_id but inform the SP that the transaction will only be created for a more limited time.

Sample response

HTTP/1.1 200 OK
Content-type: application/json


Server initiated errors

If the server-initiated request has any intimidate error that needs to be returned it will be send back with a 400. (errors triggered later are returned on the POST response.


Confirmation of the request id from the auth request.


In the event of an error the standard OpenID connect errors will be returned.


In the event of an error the standard OpenID connect errors will be returned.

carriers will be consistent with the error, but may be inconsistent with the error_description returned.

Example Server initiated error returned. see full Auth Request Errors

HTTP/1.1 400 ERROR
Content-type: application/json

"error_description":"no such user"

Server initiated callback

For a server-initiated call flow, once the MNO has secured the user authentication and authorization on the user's primary device, within the ZenKey application then the MNO will use the notification call back URL to inform the SP of the complete transaction. This is done by sending the SP the Access Token, and ID_token (if openid scope was in the request). NOTE: carriers will not support polling using the auth_req_id at this time.


Carriers will POST the responce


the carrier will post to the notification_uri that was defined in the request object and present in the clients configuration


Required: Notification token provided in request object


Required: ZenKey will not support POLLING at this time.


as defined in request object


as approved by the user


see Token endpoint



not supported yet


as defined in request


as defined in the token endpoint section below


in the event the carrier needs to communicate an error instead of a successful access_token

Carriers will be consistent


in the event the carrier needs to communicate an error instead of a successful access_token

Carriers will be inconsistent in their error_descriptions

sample callback

POST /cb HTTP/1.1
Host: <>
Content-type: application/json
Cache-control: no-cache
Authorization: bearer <notification_token>

"state":"as provided",
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc
"token_type": "Bearer",
"scope":"name email openid",
"refresh_token": "8xLOxBtZp8",
"expires_in": 3600,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc

callback error

Sample OIDC error returned on the server-initiated request

error and error descriptions will be returned on the URI.
see full Auth request errors

Note: as of version 24 of this specification the call back errors are provided in the post body not the uri.


standardized error. see Auth Request Errors

Note: invalid_grant would be returned as "server_error" for a server initiated request.


provided to indicate which request has generated this error.


Carriers are not consistent on the value of the returned error_description.


optional: carrier may return


required: carrier must return

POST /callback HTTP/1.1
Host: <>
Content-type: application/json
Cache-control: no-cache
Authorization: bearer <notification_token>

"correlation_id":"<as provided by service provider>",
"state":"<as provided by service provider>",
"error_deescription":"user has denied the request"

Server Initiated Cancel

Cancel requests are detailed in the server initiated cancel endpoint. Server-initiated Cancel

Did this page help you?