Create Payout
curl --request POST \
--url https://api.hopnow.io/v1/accounts/{account_id}/payouts \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'X-API-Key: <api-key>' \
--data '
{
"beneficiary_id": "<string>",
"payout_destination_id": "<string>",
"amount": "<string>",
"currency": "<string>",
"funding_source": {},
"payment_rail": "<string>",
"note": "<string>",
"platform_fee": {}
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/payouts"
payload = {
"beneficiary_id": "<string>",
"payout_destination_id": "<string>",
"amount": "<string>",
"currency": "<string>",
"funding_source": {},
"payment_rail": "<string>",
"note": "<string>",
"platform_fee": {}
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
'X-API-Key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
beneficiary_id: '<string>',
payout_destination_id: '<string>',
amount: '<string>',
currency: '<string>',
funding_source: {},
payment_rail: '<string>',
note: '<string>',
platform_fee: {}
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/payouts', 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://api.hopnow.io/v1/accounts/{account_id}/payouts",
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([
'beneficiary_id' => '<string>',
'payout_destination_id' => '<string>',
'amount' => '<string>',
'currency' => '<string>',
'funding_source' => [
],
'payment_rail' => '<string>',
'note' => '<string>',
'platform_fee' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>",
"X-API-Key: <api-key>"
],
]);
$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://api.hopnow.io/v1/accounts/{account_id}/payouts"
payload := strings.NewReader("{\n \"beneficiary_id\": \"<string>\",\n \"payout_destination_id\": \"<string>\",\n \"amount\": \"<string>\",\n \"currency\": \"<string>\",\n \"funding_source\": {},\n \"payment_rail\": \"<string>\",\n \"note\": \"<string>\",\n \"platform_fee\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("X-API-Key", "<api-key>")
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://api.hopnow.io/v1/accounts/{account_id}/payouts")
.header("Idempotency-Key", "<idempotency-key>")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"beneficiary_id\": \"<string>\",\n \"payout_destination_id\": \"<string>\",\n \"amount\": \"<string>\",\n \"currency\": \"<string>\",\n \"funding_source\": {},\n \"payment_rail\": \"<string>\",\n \"note\": \"<string>\",\n \"platform_fee\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopnow.io/v1/accounts/{account_id}/payouts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"beneficiary_id\": \"<string>\",\n \"payout_destination_id\": \"<string>\",\n \"amount\": \"<string>\",\n \"currency\": \"<string>\",\n \"funding_source\": {},\n \"payment_rail\": \"<string>\",\n \"note\": \"<string>\",\n \"platform_fee\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"object": "<string>",
"account_id": "<string>",
"beneficiary_id": "<string>",
"payout_destination_id": "<string>",
"network": "<string>",
"txn_hash": "<string>",
"amount": "<string>",
"currency": "<string>",
"payment_rail": {},
"amount_in_usd": "<string>",
"funding_source": {},
"platform_fee": {},
"status": "<string>",
"uetr": "<string>",
"tracking_details": {
"uetr": {},
"imad": {},
"omad": {},
"trace_number": {}
},
"initiated_at": "<string>",
"submitted_at": "<string>",
"completed_at": "<string>",
"failed_at": "<string>",
"failed_reason": "<string>",
"voided_at": "<string>",
"voided_reason": "<string>",
"note": "<string>",
"created": "<string>",
"updated": "<string>",
"created_by": "<string>",
"updated_by": "<string>"
}Payouts
Create Payout
Send money to a beneficiary’s payout destination
POST
/
v1
/
accounts
/
{account_id}
/
payouts
Create Payout
curl --request POST \
--url https://api.hopnow.io/v1/accounts/{account_id}/payouts \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'X-API-Key: <api-key>' \
--data '
{
"beneficiary_id": "<string>",
"payout_destination_id": "<string>",
"amount": "<string>",
"currency": "<string>",
"funding_source": {},
"payment_rail": "<string>",
"note": "<string>",
"platform_fee": {}
}
'import requests
url = "https://api.hopnow.io/v1/accounts/{account_id}/payouts"
payload = {
"beneficiary_id": "<string>",
"payout_destination_id": "<string>",
"amount": "<string>",
"currency": "<string>",
"funding_source": {},
"payment_rail": "<string>",
"note": "<string>",
"platform_fee": {}
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
'X-API-Key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
beneficiary_id: '<string>',
payout_destination_id: '<string>',
amount: '<string>',
currency: '<string>',
funding_source: {},
payment_rail: '<string>',
note: '<string>',
platform_fee: {}
})
};
fetch('https://api.hopnow.io/v1/accounts/{account_id}/payouts', 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://api.hopnow.io/v1/accounts/{account_id}/payouts",
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([
'beneficiary_id' => '<string>',
'payout_destination_id' => '<string>',
'amount' => '<string>',
'currency' => '<string>',
'funding_source' => [
],
'payment_rail' => '<string>',
'note' => '<string>',
'platform_fee' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>",
"X-API-Key: <api-key>"
],
]);
$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://api.hopnow.io/v1/accounts/{account_id}/payouts"
payload := strings.NewReader("{\n \"beneficiary_id\": \"<string>\",\n \"payout_destination_id\": \"<string>\",\n \"amount\": \"<string>\",\n \"currency\": \"<string>\",\n \"funding_source\": {},\n \"payment_rail\": \"<string>\",\n \"note\": \"<string>\",\n \"platform_fee\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
req.Header.Add("X-API-Key", "<api-key>")
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://api.hopnow.io/v1/accounts/{account_id}/payouts")
.header("Idempotency-Key", "<idempotency-key>")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"beneficiary_id\": \"<string>\",\n \"payout_destination_id\": \"<string>\",\n \"amount\": \"<string>\",\n \"currency\": \"<string>\",\n \"funding_source\": {},\n \"payment_rail\": \"<string>\",\n \"note\": \"<string>\",\n \"platform_fee\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hopnow.io/v1/accounts/{account_id}/payouts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"beneficiary_id\": \"<string>\",\n \"payout_destination_id\": \"<string>\",\n \"amount\": \"<string>\",\n \"currency\": \"<string>\",\n \"funding_source\": {},\n \"payment_rail\": \"<string>\",\n \"note\": \"<string>\",\n \"platform_fee\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"object": "<string>",
"account_id": "<string>",
"beneficiary_id": "<string>",
"payout_destination_id": "<string>",
"network": "<string>",
"txn_hash": "<string>",
"amount": "<string>",
"currency": "<string>",
"payment_rail": {},
"amount_in_usd": "<string>",
"funding_source": {},
"platform_fee": {},
"status": "<string>",
"uetr": "<string>",
"tracking_details": {
"uetr": {},
"imad": {},
"omad": {},
"trace_number": {}
},
"initiated_at": "<string>",
"submitted_at": "<string>",
"completed_at": "<string>",
"failed_at": "<string>",
"failed_reason": "<string>",
"voided_at": "<string>",
"voided_reason": "<string>",
"note": "<string>",
"created": "<string>",
"updated": "<string>",
"created_by": "<string>",
"updated_by": "<string>"
}Creates a payout from the trading account or from one of the account’s virtual accounts to an active destination on an active beneficiary. Hop calls the first a withdrawal and the second a payout; the API uses one object for both, told apart by
Reuse the same key and request body when retrying. Changing
The trading account may only pay a beneficiary marked
funding_source (see Withdrawals and payouts). Funds are held the moment the payout is created; Hop submits it to a provider later, so a successful response means the payout was accepted and funded, not that it has been sent.
Every successful response is 200, including the first successful create. Compare the returned id to tell a new payout from an idempotent replay.
Requires an API key with write access on the payments scope.
Path Parameters
string
required
The account’s external ID (starts with
acct_)Headers
string
required
Key for this payout: any string up to 255 characters, a UUID recommended but not enforced. Scoped to the account in the path: a key this account has used before returns that payout with
200 when the body is the same and 409 IDEMPOTENCY_CONFLICT when it differs; the same key on another account is a separate payout. See Idempotency.payment_rail, amount, beneficiary, currency or another request field returns 409 IDEMPOTENCY_CONFLICT; use a new key for a new payout. Older payouts created before payload comparison was introduced may still return the original payout without checking the body.
For an existing payout created without payment_rail, replaying the original request with payment_rail omitted or set to null remains valid. This compatibility does not allow a new virtual-account-funded payout to omit payment_rail.
Request Body
string
required
Beneficiary external ID (starts with
bene_). Must be active and belong to this account.string
required
Payout destination external ID (starts with
dest_). Must be active and belong to that beneficiary.string
required
Payout amount as a decimal string, greater than 0. It must not carry more decimal places than the currency allows.
string
required
Currency code from the accepted list. It must be enabled for payouts on the account and equal the destination’s currency: a destination pays out in one currency only.
object
Balance to fund the payout from:
{"type": "trading_account"} (a withdrawal) or {"type": "virtual_account", "virtual_account_id": "va_…"} (a payout). Defaults to the trading account when omitted. The virtual account must belong to this account, be active, and hold the payout currency.string
Required and non-null when
* Credit date varies by destination country and bank.
funding_source.type is virtual_account.Omit payment_rail for trading-account funding. Virtual-account payouts support the rails below. Cut-offs are in UTC on working days; settlement times apply to payouts submitted before the cut-off.- US
- Europe
- UK
- APAC
payment_rail | Currency | Settlement | Cut-off (UTC) |
|---|---|---|---|
ach | USD | Next business day (T+1) | 13:45–20:00 |
ach_same_day | USD | Same day (T+0); next business day after the cut-off | 13:45–20:00 |
fedwire | USD | Within 1 business day | 13:45–20:00 |
fednow | USD | Instant | 24/7 |
rtp | USD | Instant | 24/7 |
swift | USD, EUR | 1–5 business days | 07:00–14:00 |
payment_rail | Currency | Settlement | Cut-off (UTC) |
|---|---|---|---|
sepa | EUR | Within 1 business day | 07:00–14:00 |
sepa_instant | EUR | Seconds | 24/7/365 |
swift | USD, EUR | 1–5 business days | 07:00–14:00 |
payment_rail | Currency | Settlement | Cut-off (UTC) |
|---|---|---|---|
faster_payments | GBP | Minutes to hours | 24/7/365 |
swift | USD, EUR | 1–5 business days | 07:00–14:00 |
payment_rail | Currency | Settlement | Cut-off (UTC) |
|---|---|---|---|
swift | AUD, NZD | Same business day processing* | 02:00 |
swift | JPY | Same business day processing* | 02:30 |
swift | HKD | Same business day processing* | 07:00 |
swift | CNY | Same business day processing* | 07:30 |
swift | SGD | Same business day processing* | 09:00 |
swift | CHF, NOK, CAD | Same business day processing* | 10:00 |
swift | USD, EUR, GBP | Same business day processing* | 15:00 |
string
Optional note carried on the payout (max 500 characters)
object
Fee the platform charges its end user, debited from the funding source on top of
amount and credited to the platform’s fee-collection account when the payout completes (released back if the payout fails or is cancelled, but not refunded on a return). Platform customers only: end-customer accounts. Fields: currency (string, must equal the payout currency) and amount (string, > 0, at most the currency’s decimal places); unknown keys are rejected.Response
Returns200 with the payout, flat: the beneficiary and destination appear as plain id strings. List Payouts returns the expanded objects.
A new payout starts as pending, or pending_approval when an approval policy applies. It becomes processing when Hop submits it to a rail.
pending means the payout has not yet been processed. Wait for processing or completed, through Get Payout or a webhook endpoint.
string
Payout identifier (starts with
po_)string
Always returns
"payout"string
Account ID (starts with
acct_)string
Beneficiary ID (starts with
bene_). null only on payouts Hop booked manually on your behalf.string
Payout destination ID (starts with
dest_). null only on payouts Hop booked manually on your behalf.string
Blockchain network for a crypto payout, otherwise
nullstring
Blockchain transaction hash for a crypto payout, otherwise
null. It appears once the payout is on chain, so it is null on a payout that has not been submitted yet.string
Payout amount (decimal)
string
Currency code
string | null
Selected payout rail, using the lowercase values listed in Create Payout. Returned from creation onward when a rail was selected.
null means no explicit selection, including on historical payouts. This field records the selection, not confirmation of the rail actually used by the provider.string
Amount converted to USD, or
null when no rate was availableobject
Balance funding the payout, always present:
{"type": "trading_account"} for a withdrawal, {"type": "virtual_account", "virtual_account_id": "va_…"} for a payoutobject
Platform fee charged on top of
amount (currency, amount), or null when nonestring
Payout status, one of eight values:
pending: funded and queued, either awaiting Hop’s review or approved and awaiting submissionpending_approval: waiting for approval in your portal under an approval policyprocessing: submitted to the providercompleted: the provider confirmed the payment leftfailed: the payout could not be funded or the provider rejected it; the hold is releasedcancelled: cancelled before submission; the hold is releasedreturned: the payment left but the receiving bank returned itvoided: a completed payout reversed by Hop
pending → pending_approval | processing | completed | failed | cancelled; pending_approval → pending | cancelled; processing → completed | failed; completed → returned | voided. failed, cancelled, returned and voided are terminal.string
SWIFT UETR (unique end-to-end transaction reference) for a wire, or
null. Also available as tracking_details.uetr; this top-level field remains for compatibility.object
Payment-network tracking identifiers, populated as they become available. The object is always present; unavailable or inapplicable identifiers are
null. The same object is included in payout webhooks.string
ISO 8601 timestamp when the payout was initiated
string
ISO 8601 timestamp when Hop sent the payment command to the provider, or
nullstring
ISO 8601 timestamp when the payout completed, or
nullstring
ISO 8601 timestamp when the payout failed, or
nullstring
Reason for failure, or
nullstring
ISO 8601 timestamp when the payout was voided, or
nullstring
Reason the payout was voided, or
nullstring
Note carried on the payout, or
nullstring
ISO 8601 timestamp when created
string
ISO 8601 timestamp when last updated
string
Actor that created the payout: the API key id (starts with
ak_), or hop_admin for a manual payoutstring
Actor that last updated the payout, or
nullErrors
| Status | code | When |
|---|---|---|
404 | RESOURCE_NOT_FOUND | The beneficiary is unknown, deleted, or not on this account; the destination is unknown, deleted, or not on that beneficiary; funding_source.virtual_account_id does not belong to this account |
409 | IDEMPOTENCY_CONFLICT | The Idempotency-Key was already used on this account with a different payload; details.payout_id is the earlier payout |
422 | BENEFICIARY_INACTIVE | The beneficiary exists but is not active; details carry beneficiary_id and status |
422 | PAYOUT_DESTINATION_INACTIVE | The destination exists but is not active, which includes a pending one awaiting approval; details carry payout_destination_id and status |
422 | VALIDATION_ERROR | payment_rail is not a recognized enum value, amount is not positive, note exceeds 500 characters, or platform_fee carries an unknown key |
422 | MISSING_REQUIRED_FIELD | A new VA payout omits payment_rail or sends null, or a required beneficiary detail for the selected rail is missing; details.field identifies it |
422 | CURRENCY_NOT_ENABLED | currency is not enabled for payouts on this account |
422 | INVALID_FIELD_VALUE | payment_rail does not match the currency, is unsupported by the funding provider, or is supplied for trading-account funding; the destination’s routing_number is not a valid nine-digit ABA routing number on a USD local-rail VA payout; also when amount (or platform_fee.amount) has more decimal places than the currency allows |
422 | CURRENCY_MISMATCH | currency does not match the destination’s currency, the funding virtual account’s currency, or platform_fee.currency |
422 | VIRTUAL_ACCOUNT_INACTIVE | The funding virtual account is not active |
422 | TRADING_ACCOUNT_REQUIRES_SELF_OWNED_BENEFICIARY | The payout is funded from the trading account but the beneficiary is not self_owned; details.beneficiary_id and details.funding_source identify the pair |
422 | INSUFFICIENT_FUNDS | The funding source cannot cover the payout and fees under its funding policy. For trading-account funding, details.payout_id identifies the persisted failed payout |
422 | CREDIT_LIMIT_EXCEEDED | The trading-account payout fails its credit check; details include payout_id, credit_limit_usd, current_credit_used_usd, post_credit_used_usd, and drawdown_suspended |
422 | PLATFORM_FEE_NOT_ENABLED | Platform fees are not enabled for this customer |
422 | PLATFORM_FEE_NOT_ALLOWED | The account is not an end-customer account of a platform customer |
422 | PLATFORM_FEE_ACCOUNT_MISSING | The platform customer has no active fee-collection account |
500 | SERVICE_ERROR | Two identical requests with the same Idempotency-Key raced in parallel; retry sequentially |
self_owned, that is, your own bank account at another institution: a payout funded from the trading account is a withdrawal. Third parties must be paid from a virtual account. The rule is re-checked when the payout is approved, so flipping a beneficiary after creation does not bypass it.
When a payout funded from the trading account is rejected for INSUFFICIENT_FUNDS or CREDIT_LIMIT_EXCEEDED, the payout is persisted in failed state under that Idempotency-Key. The 422 response includes error.details.payout_id, and subscribed endpoints receive payout.failed. No funds are held and no payout.created event is emitted for this failure.
Retrying with the same key and body returns 200 with the same failed payout. Inspect its status; after resolving the funding issue, use a new key to create a new payout.
Request Example
{
"beneficiary_id": "bene_9gt1ummgx127biovlurm9mq2",
"payout_destination_id": "dest_7h3n5q8v2c6b1m4x9z0j5k2l",
"amount": "100.00",
"currency": "USD",
"payment_rail": "fedwire",
"funding_source": {"type": "virtual_account", "virtual_account_id": "va_2w9e4r7t1y5u8i3o6p0a5s2d"},
"note": "Monthly payment"
}
Response Example
{
"id": "po_4k2m9p1w8e5r3t6y0u2i4a7s",
"object": "payout",
"account_id": "acct_ka44qsvpo8q3wtzuwfqf0h6u",
"beneficiary_id": "bene_9gt1ummgx127biovlurm9mq2",
"payout_destination_id": "dest_7h3n5q8v2c6b1m4x9z0j5k2l",
"amount": "100.00",
"currency": "USD",
"payment_rail": "fedwire",
"amount_in_usd": "100.00",
"funding_source": {"type": "virtual_account", "virtual_account_id": "va_2w9e4r7t1y5u8i3o6p0a5s2d"},
"platform_fee": null,
"status": "pending",
"uetr": null,
"tracking_details": {
"uetr": null,
"imad": null,
"omad": null,
"trace_number": null
},
"network": null,
"txn_hash": null,
"initiated_at": "2026-01-15T10:00:00Z",
"submitted_at": null,
"completed_at": null,
"failed_at": null,
"failed_reason": null,
"voided_at": null,
"voided_reason": null,
"note": "Monthly payment",
"created": "2026-01-15T10:00:00Z",
"updated": "2026-01-15T10:00:00Z",
"created_by": "ak_3f7k9m2p5r8t1v4x6z0b3n5q",
"updated_by": null
}
status is pending_approval here only when an approval policy is configured for your organization.