Transaction Authorization
Sign payouts with the employee's wallet when the company runs in Signature mode.
Before You Start
Read the following guides before proceeding:
| Guide | Why |
|---|---|
| Wallet Signatures | Who signs, and how each wallet type encodes the signature |
| Permissions | Permissions are checked before the signature |
| Environments | GET /api/v1/config returns the company's authorization mode |
Overview
A company runs in one of two transaction authorization modes:
| Mode | Payouts |
|---|---|
External | Executed without a wallet signature. transaction_authorization is not required |
Signature | Each payout carries transaction_authorization: a wallet signature over a fixed sentence that names the operation, the estimation and a nonce |
The signature is checked after request validation and before anything is executed, so a refused request
moves no funds. Each signature is accepted once.
Reading the Mode
GET /api/v1/config
Send the company id as a UUID (00000000-0000-0000-0000-000000000012, not 0x…) in the X-Company-Id
header. No token is required. The full response is described in
Environments — Runtime Configuration.
{
"defaultCompanyId": "00000000-0000-0000-0000-000000000012",
"transactionAuthorizationMode": "External"
}| Field | Description |
|---|---|
transactionAuthorizationMode | External or Signature. Only Signature requires transaction_authorization |
Endpoints
| Method | Path | Permissions (any of) | Signature | Sentence |
|---|---|---|---|---|
POST | /api/v1/cards/transfer | CardSu | Required in Signature mode | Card Transfer |
POST | /api/v3/bank/initiate/{corridor} | TransactionSu, TransactionCreate | Required in Signature mode | Corridor Transfers |
POST | /api/v1/bank/transfer | TransactionSu, TransactionCreate | Required in Signature mode | Bank Transfers |
POST | /api/v2/recipients | TransactionSu, TransactionManage | Never required. Checked when sent, if the company is in Signature mode and is an agent company (operating mode Agentic or Mixed). Ignored otherwise | Creating a Recipient |
All four accept the corporation token only. A caller without the permission receives 403 before the
signature is looked at.
Estimate, confirm and read endpoints take no signature. POST /api/v1/bulk/transfer takes none: bulk
transfers are signed on-chain by the wallet. The other recipient routes — POST /api/v1/recipients,
POST /api/v1/recipients/{recipientId}/payment_details, PUT /api/v1/recipients/{recipientId},
PUT /api/v1/recipients/{recipientId}/payment_details/{paymentDetailsId} and
PUT /api/v2/recipients/{recipientId} — take none.
The signer is the employee_address wallet, and an employee signs with their own wallet, never the
owner's. Encodings per wallet type are in Wallet Signatures.
The transaction_authorization Object
transaction_authorization Object{
"transaction_authorization": {
"message_signature": "0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac81f0c5b0e4a7c1d2e3f405162738495a6b7c8d9e0f1a2b3c4d5e6f708192a3b4c1b",
"nonce": 1760774400
}
}| Field | Type | Required | Description |
|---|---|---|---|
transaction_authorization | object | Conditional | Required on the three payout endpoints when the mode is Signature. Optional on POST /api/v2/recipients. When sent, its shape is validated in every mode |
transaction_authorization.message_signature | string | Yes | Signature over the sentence |
transaction_authorization.nonce | integer (int64) | Yes | Unix time in seconds used in the sentence. Not 0, not more than 5 minutes old, not more than 60 seconds in the future |
transaction_authorization.public_key | string | Conditional | Required for XRPL wallets: the signer's public key in hex, ^[0-9A-Fa-f]{2,200}$, starting with ED (ed25519) or 02/03 (secp256k1). Ignored for EVM and Stellar |
A signature is spent on first use, including when the execution behind it fails. Every retry needs a
new signature with a newnonce.
The Sentence
Each endpoint's page gives the sentence to sign, character for character. The placeholders follow the same
rules everywhere:
| Placeholder | Value |
|---|---|
<estimation_id> | The request's estimation_id as a lowercase, hyphenated UUID, e.g. 550e8400-e29b-41d4-a716-446655440000, whatever case or format the request sends |
<nonce> | The same integer as transaction_authorization.nonce |
Sign the sentence with the employee_address wallet — see Wallet Signatures.
Error Handling
{
"error_reason": "ErrorPermissionDenied",
"error_description": "transaction authorization does not authorize this request",
"error_category": {
"category": "CategoryUnauthorized",
"http_status_code": 401
}
}Validation Errors (400)
| Error Reason | Error Details | Description | Resolution |
|---|---|---|---|
ErrorMissingField | field: message_signature, issue: missing | message signature is required | transaction_authorization was sent without a signature. Any mode |
ErrorMissingField | field: nonce, issue: missing | nonce is required | nonce is missing or 0. Any mode |
ErrorInvalidField | field: public_key, issue: invalid_format | Invalid public key format | public_key is not hex. Any mode |
ErrorInvalidField | field: public_key, issue: invalid_format | Invalid public key | Signature mode: public_key is hex but not an XRPL public key |
ErrorMissingField | field: public_key, issue: missing | public key is required for Ripple signatures | Signature mode, XRPL wallet, no public_key |
ErrorInvalidField | field: message_signature, issue: invalid_format | Invalid message signature | Signature mode: the signature cannot be decoded or recovers to no address — including a smart-contract wallet signature that the wallet declines. Fix the encoding or sign again |
Authorization Errors (401)
| Error Reason | Error Details | Description | Resolution |
|---|---|---|---|
ErrorMissingField | field: transaction_authorization, issue: missing | transaction authorization is required | Signature mode, payout sent without transaction_authorization. Sign the sentence and send it |
ErrorExpired | field: nonce, issue: expired | transaction authorization has expired | nonce is more than 5 minutes old. Sign again with a new nonce |
ErrorNotYetValid | field: nonce, issue: not_yet_valid | transaction authorization is not yet valid, check the device clock | nonce is more than 60 seconds in the future. The signing device's clock is wrong |
ErrorPermissionDenied | — | transaction authorization does not authorize this request | Another estimation, operation, recipient or wallet, or a sentence that differs by any character. The response does not say which. Rebuild the sentence and sign with the employee_address wallet |
ErrorAlreadyExists | field: transaction_authorization, issue: already_used | transaction authorization has already been used | Sign again with a new nonce |
Server Errors
| Error Reason | HTTP | Description | Resolution |
|---|---|---|---|
ErrorNotSupported | 500 | Unsupported chain type | The wallet's chain type cannot be verified |
ErrorGeneral | 503 | Transaction authorization is temporarily unavailable | Nothing was executed. Retry with a new signature |
Updated about 2 hours ago

