curl --request POST \
--url https://{tenant_id}.hanko.io/token_exchange \
--header 'Content-Type: application/json' \
--data '
{
"input_data": {
"token": "<string>"
},
"csrf_token": "qvcZt29spXYO77Y9IaxxN4MzLnmbjozl"
}
'import requests
url = "https://{tenant_id}.hanko.io/token_exchange"
payload = {
"input_data": { "token": "<string>" },
"csrf_token": "qvcZt29spXYO77Y9IaxxN4MzLnmbjozl"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
input_data: {token: '<string>'},
csrf_token: 'qvcZt29spXYO77Y9IaxxN4MzLnmbjozl'
})
};
fetch('https://{tenant_id}.hanko.io/token_exchange', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{tenant_id}.hanko.io/token_exchange",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'input_data' => [
'token' => '<string>'
],
'csrf_token' => 'qvcZt29spXYO77Y9IaxxN4MzLnmbjozl'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://{tenant_id}.hanko.io/token_exchange"
payload := strings.NewReader("{\n \"input_data\": {\n \"token\": \"<string>\"\n },\n \"csrf_token\": \"qvcZt29spXYO77Y9IaxxN4MzLnmbjozl\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://{tenant_id}.hanko.io/token_exchange")
.header("Content-Type", "application/json")
.body("{\n \"input_data\": {\n \"token\": \"<string>\"\n },\n \"csrf_token\": \"qvcZt29spXYO77Y9IaxxN4MzLnmbjozl\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{tenant_id}.hanko.io/token_exchange")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"input_data\": {\n \"token\": \"<string>\"\n },\n \"csrf_token\": \"qvcZt29spXYO77Y9IaxxN4MzLnmbjozl\"\n}"
response = http.request(request)
puts response.read_body{
"actions": {
"verify_passcode": {
"action": "verify_passcode",
"href": "<string>",
"description": "<string>",
"inputs": {
"code": {
"name": "code",
"type": "string",
"required": true,
"min_length": 6,
"max_length": 6,
"hidden": false,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"resend_passcode": {
"action": "resend_passcode",
"href": "<string>",
"description": "<string>",
"inputs": {}
},
"back": {
"action": "back",
"href": "<string>",
"description": "<string>",
"inputs": {}
}
},
"name": "passcode_confirmation",
"payload": {},
"status": 200,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": []
}{
"actions": {
"continue_with_login_identifier": {
"action": "continue_with_login_identifier",
"href": "<string>",
"description": "<string>",
"inputs": {
"identifier": {
"name": "identifier",
"type": "string",
"required": true,
"min_length": 123,
"max_length": 255,
"hidden": false,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"webauthn_generate_request_options": {
"action": "webauthn_generate_request_options",
"href": "<string>",
"description": "<string>",
"inputs": {}
},
"webauthn_verify_assertion_response": {
"action": "webauthn_verify_assertion_response",
"href": "<string>",
"description": "<string>",
"inputs": {
"assertion_response": {
"name": "assertion_response",
"type": "json",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"thirdparty_oauth": {
"action": "thirdparty_oauth",
"href": "<string>",
"description": "<string>",
"inputs": {
"provider": {
"name": "provider",
"type": "string",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "apple"
}
]
},
"redirect_to": {
"name": "redirect_to",
"type": "string",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
},
"code_verifier": {
"name": "code_verifier",
"type": "string",
"required": false,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"remember_me": {
"action": "remember_me",
"href": "<string>",
"description": "<string>",
"inputs": {
"remember_me": {
"name": "remember_me",
"type": "boolean",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
}
},
"name": "login_init",
"payload": {
"request_options": {
"publicKey": {
"challenge": "qgOI+0KpGnl9NOqaT6dfsYvi96R87LgpErnvePeOgSU=",
"timeout": 60000,
"rpId": "localhost",
"allowCredentials": [
{
"type": "public-key",
"id": "Mepptysj5ZZrTlg0qiLbsZ068OtQMeGVAikVy2n1hvvG..."
}
],
"userVerification": "required"
}
}
},
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "<string>",
"message": "<string>",
"cause": "<string>"
}
}{
"actions": {},
"name": "error",
"payload": {},
"status": 401,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "passcode_max_attempts_reached",
"message": "The passcode was entered wrong too many times."
}
}{
"actions": {},
"name": "error",
"payload": {},
"status": 403,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "operation_not_permitted_error",
"message": "The flow is not permitted."
}
}{
"actions": {},
"name": "error",
"payload": {
"resend_after": 60
},
"status": 429,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "rate_limit_exceeded",
"message": "The rate limit has been exceeded."
}
}{
"actions": {},
"name": "error",
"payload": {},
"status": 500,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "technical_error",
"message": "Something went wrong."
}
}Token exchange
Initialize or advance a token exchange flow.
curl --request POST \
--url https://{tenant_id}.hanko.io/token_exchange \
--header 'Content-Type: application/json' \
--data '
{
"input_data": {
"token": "<string>"
},
"csrf_token": "qvcZt29spXYO77Y9IaxxN4MzLnmbjozl"
}
'import requests
url = "https://{tenant_id}.hanko.io/token_exchange"
payload = {
"input_data": { "token": "<string>" },
"csrf_token": "qvcZt29spXYO77Y9IaxxN4MzLnmbjozl"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
input_data: {token: '<string>'},
csrf_token: 'qvcZt29spXYO77Y9IaxxN4MzLnmbjozl'
})
};
fetch('https://{tenant_id}.hanko.io/token_exchange', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://{tenant_id}.hanko.io/token_exchange",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'input_data' => [
'token' => '<string>'
],
'csrf_token' => 'qvcZt29spXYO77Y9IaxxN4MzLnmbjozl'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://{tenant_id}.hanko.io/token_exchange"
payload := strings.NewReader("{\n \"input_data\": {\n \"token\": \"<string>\"\n },\n \"csrf_token\": \"qvcZt29spXYO77Y9IaxxN4MzLnmbjozl\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://{tenant_id}.hanko.io/token_exchange")
.header("Content-Type", "application/json")
.body("{\n \"input_data\": {\n \"token\": \"<string>\"\n },\n \"csrf_token\": \"qvcZt29spXYO77Y9IaxxN4MzLnmbjozl\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://{tenant_id}.hanko.io/token_exchange")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"input_data\": {\n \"token\": \"<string>\"\n },\n \"csrf_token\": \"qvcZt29spXYO77Y9IaxxN4MzLnmbjozl\"\n}"
response = http.request(request)
puts response.read_body{
"actions": {
"verify_passcode": {
"action": "verify_passcode",
"href": "<string>",
"description": "<string>",
"inputs": {
"code": {
"name": "code",
"type": "string",
"required": true,
"min_length": 6,
"max_length": 6,
"hidden": false,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"resend_passcode": {
"action": "resend_passcode",
"href": "<string>",
"description": "<string>",
"inputs": {}
},
"back": {
"action": "back",
"href": "<string>",
"description": "<string>",
"inputs": {}
}
},
"name": "passcode_confirmation",
"payload": {},
"status": 200,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": []
}{
"actions": {
"continue_with_login_identifier": {
"action": "continue_with_login_identifier",
"href": "<string>",
"description": "<string>",
"inputs": {
"identifier": {
"name": "identifier",
"type": "string",
"required": true,
"min_length": 123,
"max_length": 255,
"hidden": false,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"webauthn_generate_request_options": {
"action": "webauthn_generate_request_options",
"href": "<string>",
"description": "<string>",
"inputs": {}
},
"webauthn_verify_assertion_response": {
"action": "webauthn_verify_assertion_response",
"href": "<string>",
"description": "<string>",
"inputs": {
"assertion_response": {
"name": "assertion_response",
"type": "json",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"thirdparty_oauth": {
"action": "thirdparty_oauth",
"href": "<string>",
"description": "<string>",
"inputs": {
"provider": {
"name": "provider",
"type": "string",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "apple"
}
]
},
"redirect_to": {
"name": "redirect_to",
"type": "string",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
},
"code_verifier": {
"name": "code_verifier",
"type": "string",
"required": false,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
},
"remember_me": {
"action": "remember_me",
"href": "<string>",
"description": "<string>",
"inputs": {
"remember_me": {
"name": "remember_me",
"type": "boolean",
"required": true,
"min_length": 123,
"max_length": 123,
"hidden": true,
"value": "<string>",
"allowed_values": [
{
"name": "<string>",
"value": "<unknown>"
}
]
}
}
}
},
"name": "login_init",
"payload": {
"request_options": {
"publicKey": {
"challenge": "qgOI+0KpGnl9NOqaT6dfsYvi96R87LgpErnvePeOgSU=",
"timeout": 60000,
"rpId": "localhost",
"allowCredentials": [
{
"type": "public-key",
"id": "Mepptysj5ZZrTlg0qiLbsZ068OtQMeGVAikVy2n1hvvG..."
}
],
"userVerification": "required"
}
}
},
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "<string>",
"message": "<string>",
"cause": "<string>"
}
}{
"actions": {},
"name": "error",
"payload": {},
"status": 401,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "passcode_max_attempts_reached",
"message": "The passcode was entered wrong too many times."
}
}{
"actions": {},
"name": "error",
"payload": {},
"status": 403,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "operation_not_permitted_error",
"message": "The flow is not permitted."
}
}{
"actions": {},
"name": "error",
"payload": {
"resend_after": 60
},
"status": 429,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "rate_limit_exceeded",
"message": "The rate limit has been exceeded."
}
}{
"actions": {},
"name": "error",
"payload": {},
"status": 500,
"csrf_token": "HvUwWSfPgz7VnwiS8VMDpnhZ1wNwTNiV",
"links": [],
"error": {
"code": "technical_error",
"message": "Something went wrong."
}
}Headers
Used to internationalize outgoing emails (e.g. for email verification, recovery, etc.).
If email delivery by Hanko is enabled the values for supported languages are:
- "bn" (Bengali/Bangla)
- "de" (German)
- "en" (English)
- "fr" (French)
- "it" (Italian)
- "nl" (Dutch)
- "pt-BR" (Brazilian Portuguese),
- "zh" (Chinese)
If email delivery by Hanko is disabled and a webhook has been configured for the email.send event, the
JWT payload of the token contained in the response to the webhook endpoint contains a language claim that
reflects the value originally passed as the header value.
bn, de, en, fr, it, nl, pt-BR, zh Query Parameters
String of the format {action_name}@{flow_id}. Indicates the action to perform on the flow with the
given flow_id. Omitting the query parameter initializes a new flow.
Note for playground usage: You can derive the value for this query parameter from the action's href
property in a flow state response, e.g. for an href value of
/login?action=register_client_capabilities%4015655672-41ca-48cc-afb1-90be77075764 the (non-URL-encoded) value
would be register_client_capabilities@15655672-41ca-48cc-afb1-90be77075764.
"register_client_capabilities@15655672-41ca-48cc-afb1-90be77075764"
Body
TokenExchangeRequestBody
Not required on flow initialization, i.e. on requests without an action
query parameter.
Required on all other requests performing an action, i.e. on requests that use an action
query parameter. Should be the csrf_token value from the most recent flow state response.
"qvcZt29spXYO77Y9IaxxN4MzLnmbjozl"
Response
TokenExchangeFlowResponse
- PasscodeConfirmation
- OnboardingUsername
- ThirdParty
- Success
Additional data that can be used by the client (e.g. user or sessions data provided in the
profile flow) or should/must be used as intermediary data in an out of band process to produce input data
for advancing the flow (e.g. the WebAuthn credential request/creation options that must be passed to the
Webauthn API to produce an assertion/attestation).