PayoutID API
Payout OIDC, OAuth2 and verification server.
- OAuth2: redirect users to PayoutID for authorization, then exchange the result for an access token. Tokens can also be refreshed or issued to your own client with client credentials.
- Verifications: create identity verification invitations for your customers and receive the results by webhook.
Guides: OAuth2 and identity verification.
Authentication
Payout registers your application as a PayoutID client and gives you a client_id (a UUID) and a client_secret. The client also has its registered redirect URIs and the scopes it may request.
Token endpoint. Authenticate your client at POST /oauth/token in one of these ways:
client_secret_basic: HTTP Basic authentication withclient_idas the user name andclient_secretas the password.client_secret_post:client_secretas a body parameter.
Both are enabled for every client by default. Always send client_id in the body as well. Clients that Payout configured for client_secret_jwt or private_key_jwt send a signed JWT in client_assertion instead of the secret.
Verifications API. Send the access token returned by the token endpoint in the Authorization header:
Authorization: Bearer <access_token>
Access tokens are JWTs signed by PayoutID. A token works only in the environment that issued it: https://id-sa.payout.one for Sandbox, https://id.payout.one for Production.
Errors
Token endpoint. Errors are JSON objects with error and error_description:
{
"error": "invalid_grant",
"error_description": "Given authorization code is invalid, revoked, or expired."
}
| Status | error |
Meaning |
|---|---|---|
| 400 | invalid_request |
A parameter is missing or malformed, or the PKCE code verifier is wrong |
| 400 | invalid_grant |
The authorization code or refresh token is invalid, revoked or expired |
| 400 | invalid_scope |
A requested scope is not enabled for your client or not allowed for this grant |
| 400 | unsupported_grant_type |
The grant type is not enabled for your client |
| 401 | invalid_client |
Unknown client, wrong client secret or redirect URI that does not match |
| 500 | unknown_error |
Unexpected server error |
Authorization redirect. Errors are sent to your redirect_uri as the error, error_description and state query parameters. If the user denies access, you get error=access_denied. If client_id or redirect_uri is invalid, PayoutID cannot redirect back safely and shows an error page to the user.
Verifications API.
| Status | Body | Meaning |
|---|---|---|
| 403 | Plain text UNAUTHORIZED: Missing or insuficient authorization |
The token is missing, invalid or expired, or it lacks the verify scope |
| 422 | {"errors": {"<field>": ["<message>"]}} |
Validation failed |
Endpoint where client should redirect user for authorization.
Open this URL in the user's browser; it is not an API call. PayoutID asks the user to log in or register if needed, then to approve the requested scopes. Afterwards it redirects the browser back to redirect_uri:
- on success, with
code(the authorization code) andstate; - on error or when the user denies access, with
error,error_description(when available) andstate.
The authorization code is valid for at most 60 seconds and can be used once. Exchange it at POST /oauth/token with grant_type=authorization_code.
Parameters
c24760a3-134f-4ff5-891b-e506a025a530code (authorization code flow). one of codehttps://www.example.comopenid profileE9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cMS256; plain is also accepted. one of S256, plain · e.g. S256c0416c89-02fb-4c64-b4f6-9ebee510f4a0Responses
redirect_uri with the authorization response.curl -X GET 'https://id-sa.payout.one/oauth/authorize?client_id=c24760a3-134f-4ff5-891b-e506a025a530&response_type=code&redirect_uri=https://www.example.com'const res = await fetch("https://id-sa.payout.one/oauth/authorize?client_id=c24760a3-134f-4ff5-891b-e506a025a530&response_type=code&redirect_uri=https://www.example.com", {
method: "GET",
});
const data = await res.json();import os, requests
res = requests.get(
"https://id-sa.payout.one/oauth/authorize?client_id=c24760a3-134f-4ff5-891b-e506a025a530&response_type=code&redirect_uri=https://www.example.com",
)
data = res.json()<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://id-sa.payout.one/oauth/authorize?client_id=c24760a3-134f-4ff5-891b-e506a025a530&response_type=code&redirect_uri=https://www.example.com");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
$data = json_decode(curl_exec($ch), true);
curl_close($ch);Endpoint where client can retrieve authorization token after successful authorization, refresh an expired access token with a valid refresh token, or get a token for itself with client credentials.
grant_type |
Use | Parameters |
|---|---|---|
authorization_code |
Exchange the code from the authorization redirect |
code, redirect_uri; code_verifier when PKCE is enabled for your client |
refresh_token |
Get a new access token for the same user and client | refresh_token; optional scope |
client_credentials |
Get a token for your own client, without a user. The Verifications API needs scope verify. |
optional scope |
Every grant needs client_id in the body. The client secret (HTTP Basic or client_secret in the body) is required for client_credentials, for refresh_token unless public refresh is enabled for your client, and for authorization_code when your client is confidential.
A refresh revokes the refresh token that was used, and the response carries a new one. id_token is returned by the authorization_code grant when the openid scope was granted. Scopes that only a user can grant, such as PISPSUBMIT, cannot be requested with client_credentials.
The body can be sent as application/x-www-form-urlencoded or as JSON.
Request body
authorization_code, refresh_token, client_credentialsc24760a3-134f-4ff5-891b-e506a025a530client_secret_post). Omit it when you authenticate with HTTP Basic.authorization_code: the code from the authorization redirect. e.g. 63a74bf4-fa3d-4b21-8350-8c51ae47d74aauthorization_code: the same redirect_uri that was used in the authorization request. e.g. https://www.example.comauthorization_code: PKCE code verifier. Required when PKCE is enabled for your client. e.g. dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXkrefresh_token: refresh token from a previous token response. e.g. 4b217f27-836a-4a12-97a3-b962c23dedc5client_credentials: each scope must be enabled for your client. refresh_token: optional, at most the scopes of the original token, which are used when omitted. e.g. verify account_infoclient_secret_jwt or private_key_jwt. one of urn:ietf:params:oauth:client-assertion-type:jwt-bearerclient_secret_jwt or private_key_jwt. Signed JWT with sub set to your client ID, aud set to the PayoutID URL of the environment, and iss and exp claims.Response 200
Authorization: Bearer <access_token>.bearer86400refresh_token grant.authorization_code grant when the openid scope was granted.Other responses
curl -X POST 'https://id-sa.payout.one/oauth/token' \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"client_id": "c24760a3-134f-4ff5-891b-e506a025a530",
"code": "63a74bf4-fa3d-4b21-8350-8c51ae47d74a",
"redirect_uri": "https://www.example.com",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}'const res = await fetch("https://id-sa.payout.one/oauth/token", {
method: "POST",
headers: {
Authorization: "Basic " + Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString("base64"),
"Content-Type": "application/json",
},
body: JSON.stringify({
"grant_type": "authorization_code",
"client_id": "c24760a3-134f-4ff5-891b-e506a025a530",
"code": "63a74bf4-fa3d-4b21-8350-8c51ae47d74a",
"redirect_uri": "https://www.example.com",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
}),
});
const data = await res.json();import os, requests
res = requests.post(
"https://id-sa.payout.one/oauth/token",
auth=(os.environ["CLIENT_ID"], os.environ["CLIENT_SECRET"]),
json={
"grant_type": "authorization_code",
"client_id": "c24760a3-134f-4ff5-891b-e506a025a530",
"code": "63a74bf4-fa3d-4b21-8350-8c51ae47d74a",
"redirect_uri": "https://www.example.com",
"code_verifier": "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk",
},
)
data = res.json()<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://id-sa.payout.one/oauth/token");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_USERPWD, getenv("CLIENT_ID") . ":" . getenv("CLIENT_SECRET"));
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"grant_type" => "authorization_code",
"client_id" => "c24760a3-134f-4ff5-891b-e506a025a530",
"code" => "63a74bf4-fa3d-4b21-8350-8c51ae47d74a",
"redirect_uri" => "https://www.example.com",
"code_verifier" => "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9.eyJpc3MiOiJodHRwczovL2lkLXNhLnBheW91dC5vbmUifQ.…",
"token_type": "bearer",
"expires_in": 86400,
"refresh_token": "4b217f27-836a-4a12-97a3-b962c23dedc5"
}Invitation to identity verification
Creates URL to which you can redirect customer to get him verified.
Use an access token from the client_credentials grant with scope verify. Add scope account_info to request bank account details (bank_account_requested) and scope aml to request an AML check (aml_requested).
When all data are retrieved, PayoutID sends the user details as a webhook to notify_url, and AML check results to aml_notify_url. The identity verification guide describes the webhooks and how to verify their signatures.
Request body
john.doe@example.comJohnDoeaccount_info. default falseaml and aml_notify_url. default falseCZ6508000000192000145399https://example.comhttps://example.com/webhooks/payout-idaml_requested is true. e.g. https://example.com/webhooks/payout-id-amlResponse 200
Other responses
verify scope. The body is plain text.bank_account_requested / aml_requested without the matching scope (missing scope for condition).curl -X POST 'https://id-sa.payout.one/api/v1/verification/invitation' \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_provided_iban": "CZ6508000000192000145399",
"bank_account_requested": true,
"callback_url": "https://example.com",
"notify_url": "https://example.com/webhooks/payout-id"
}'const res = await fetch("https://id-sa.payout.one/api/v1/verification/invitation", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYOUT_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"client_provided_iban": "CZ6508000000192000145399",
"bank_account_requested": true,
"callback_url": "https://example.com",
"notify_url": "https://example.com/webhooks/payout-id"
}),
});
const data = await res.json();import os, requests
res = requests.post(
"https://id-sa.payout.one/api/v1/verification/invitation",
headers={"Authorization": f"Bearer {os.environ['PAYOUT_TOKEN']}"},
json={
"client_provided_iban": "CZ6508000000192000145399",
"bank_account_requested": True,
"callback_url": "https://example.com",
"notify_url": "https://example.com/webhooks/payout-id",
},
)
data = res.json()<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://id-sa.payout.one/api/v1/verification/invitation");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"client_provided_iban" => "CZ6508000000192000145399",
"bank_account_requested" => true,
"callback_url" => "https://example.com",
"notify_url" => "https://example.com/webhooks/payout-id"
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer " . getenv("PAYOUT_TOKEN"), "Content-Type: application/json"]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);{
"id": "f2a060df-4812-4335-8706-1c29e61c8b47",
"redirect_url": "https://id-sa.payout.one/verifications/f2a060df-4812-4335-8706-1c29e61c8b47"
}- Need help? Contact support.
- Questions? Contact sales.
- Service status? status.payout.one.
- LLM? Read llms.txt.