Simulate Payin
curl --request POST \
--url https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'X-API-Key: <api-key>' \
--data '
{
"deposit_destination": {},
"currency": "<string>",
"amount": "<string>",
"sender_name": "<string>",
"note": "<string>"
}
'import requests
url = "https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations"
payload = {
"deposit_destination": {},
"currency": "<string>",
"amount": "<string>",
"sender_name": "<string>",
"note": "<string>"
}
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({
deposit_destination: {},
currency: '<string>',
amount: '<string>',
sender_name: '<string>',
note: '<string>'
})
};
fetch('https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations', 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.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations",
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([
'deposit_destination' => [
],
'currency' => '<string>',
'amount' => '<string>',
'sender_name' => '<string>',
'note' => '<string>'
]),
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.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations"
payload := strings.NewReader("{\n \"deposit_destination\": {},\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"note\": \"<string>\"\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.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations")
.header("Idempotency-Key", "<idempotency-key>")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"deposit_destination\": {},\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"note\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations")
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 \"deposit_destination\": {},\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"note\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyPayins
Simulate Payin
Credit a balance in sandbox without a real deposit, and receive the payin.received webhook
POST
/
v1
/
accounts
/
{account_id}
/
payin-simulations
Simulate Payin
curl --request POST \
--url https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--header 'X-API-Key: <api-key>' \
--data '
{
"deposit_destination": {},
"currency": "<string>",
"amount": "<string>",
"sender_name": "<string>",
"note": "<string>"
}
'import requests
url = "https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations"
payload = {
"deposit_destination": {},
"currency": "<string>",
"amount": "<string>",
"sender_name": "<string>",
"note": "<string>"
}
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({
deposit_destination: {},
currency: '<string>',
amount: '<string>',
sender_name: '<string>',
note: '<string>'
})
};
fetch('https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations', 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.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations",
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([
'deposit_destination' => [
],
'currency' => '<string>',
'amount' => '<string>',
'sender_name' => '<string>',
'note' => '<string>'
]),
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.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations"
payload := strings.NewReader("{\n \"deposit_destination\": {},\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"note\": \"<string>\"\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.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations")
.header("Idempotency-Key", "<idempotency-key>")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"deposit_destination\": {},\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"note\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sbx.hopnow.io/v1/accounts/{account_id}/payin-simulations")
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 \"deposit_destination\": {},\n \"currency\": \"<string>\",\n \"amount\": \"<string>\",\n \"sender_name\": \"<string>\",\n \"note\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodySandbox and development only. Production does not register this route: a call there returns
Unknown fields are rejected with
404 RESOURCE_NOT_FOUND in the standard error envelope, and the endpoint is absent from the production OpenAPI document.
Credits a balance as though funds had arrived, so you can exercise the receive side of your integration without a bank transfer or an on-chain send. The payin runs through the same lifecycle a real deposit does, lands completed, and raises the same payin.received webhook.
No provider is contacted and no funds are swept. The credit is real money in sandbox terms: it lands on the balance and can fund a payout, a transfer or an FX trade.
Requires write on the payments scope.
Path Parameters
string
required
The account’s external ID (starts with
acct_)Headers
string
required
Canonical
8-4-4-4-12 UUID, scoped to the account and compared byte-for-byte, as on every idempotent endpoint: A1B2… and a1b2… are different keys. A replay with the same body returns 200 with the original payin; with a different body, 409 IDEMPOTENCY_CONFLICT. See Idempotency.Request Body
object
required
The balance to credit:
{"type": "trading_account"} or {"type": "virtual_account", "virtual_account_id": "va_…"}.A virtual account must belong to this account, be active, and hold currency.string
required
Currency code. For a
trading_account destination it must be enabled for payins on the account; a currency that is not returns 422 CURRENCY_NOT_ENABLED. A virtual_account destination is checked against the virtual account’s own currency instead.string
required
Greater than 0, at most 18 digits and 6 decimal places, and within the currency’s own precision. See Handling currencies.
string
Name to record as the sender, max 140 characters. Surfaces as
sender_name on the payin.string
Remittance reference to record, 1 to 255 characters. Defaults to
Simulated deposit.422 VALIDATION_ERROR.
Response
201 with the completed Payin on creation, 200 with the original on an idempotent replay.
The payin is indistinguishable from a real one. Set sender_name or note if you need to tell simulated credits apart when reconciling a sandbox account.
{
"id": "pi_uh0p51dqek7qes5e52a6x7of",
"object": "payin",
"account_id": "acct_ka44qsvpo8q3wtzuwfqf0h6u",
"amount": "1000.00",
"currency": "USD",
"network": null,
"amount_in_usd": "1000.00",
"status": "completed",
"payment_method": null,
"note": "Simulated deposit",
"txn_hash": null,
"destination_wallet_id": null,
"destination_address": null,
"source_address": null,
"sender_name": "ACME TRADING LTD",
"sender_account_masked": null,
"sender_bank_code": null,
"sender_country": null,
"deposit_destination": { "type": "trading_account" },
"payin_type": "standard",
"initiated_at": "2026-01-15T10:00:00Z",
"completed_at": "2026-01-15T10:00:00Z",
"failed_at": null, "failed_reason": null, "voided_at": null, "voided_reason": null,
"created": "2026-01-15T10:00:00Z",
"updated": "2026-01-15T10:00:00Z",
"created_by": "ak_3f7k9m2p5r8t1v4x6z0b3n5q",
"updated_by": null
}
Errors
| Status | code | When |
|---|---|---|
404 | RESOURCE_NOT_FOUND | virtual_account_id is unknown or on another account, or the route was called in production, where it is not registered |
409 | IDEMPOTENCY_CONFLICT | The key was reused with a different body |
422 | VALIDATION_ERROR | Idempotency-Key missing or not a canonical UUID, amount not positive or over 6 decimal places, an unknown field, or a value over its maximum length |
422 | INVALID_FIELD_VALUE | amount carries more decimal places than the currency allows |
422 | CURRENCY_NOT_ENABLED | Trading-account destination in a currency not enabled for payins on the account |
422 | VIRTUAL_ACCOUNT_INACTIVE | The destination virtual account is not active |
422 | CURRENCY_MISMATCH | currency does not match the destination virtual account’s currency |
Examples
{
"deposit_destination": { "type": "trading_account" },
"currency": "USD",
"amount": "1000.00",
"sender_name": "ACME TRADING LTD",
"note": "INV-2291"
}
{
"deposit_destination": {
"type": "virtual_account",
"virtual_account_id": "va_7c3e9a1d5b2f8e4a6c0d3b7f"
},
"currency": "EUR",
"amount": "250.00"
}
What this unblocks
Sandbox has no way to make a real deposit arrive, so before this endpoint the receive side could only be exercised against real provider activity. With it you can test:- A
payin.receivedwebhook reaching your handler, signed the same way a production delivery is. See Webhook security. - Funding a sandbox account for a payout, transfer or FX test without asking support to top it up.
- Crediting a named virtual account, including the
deposit_destinationshape your reconciliation reads. - Fiat credits, which have no sandbox equivalent at all: no bank sends money to a sandbox deposit instruction.