Send a same-day US domestic wire
Save a US account as a wire payee with its wire routing number and send a same-day Fedwire payment with a 35-character message.
Use this recipe to save a US account as a wire payee with its wire routing number and send a same-day Fedwire payment with a message of up to 35 characters.
Who it is for. A US property manager wiring a 48,000 USD closing payment to a title company's escrow account.
Status. Available from bank-held and wallet-backed USD accounts.
Money example
- The customer's USD account holds 60,000.00 USD.
- The customer wires 48,000.00 USD to Lakeside Title LLC. In this example, the fee on your plan is 15.00 USD.
- Gravv debits the account 48,015.00 USD. Lakeside receives 48,000.00 USD the same day.
Before you start
- Everything in Pay a US bank account by ACH.
- The receiving bank's wire routing number. Some banks use a different ABA number for wires than for ACH.
Find out what is required
The payee has the same fields as for ACH, with "payment_rail": "wire". When you save the payee, Gravv checks the routing number against the wire directory. If the routing number cannot receive wires, the response says so.
Steps
1. Save the wire payee
curl --request POST \
--url https://api.gravv.xyz/v1/external-accounts \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: bfd14696-c472-43f8-88ce-960042cf9499' \
--data '
{
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"payee_type": "ach_wire",
"currency": "USD",
"account_name": "Lakeside Title LLC",
"account_number": "4400918273",
"routing_number": "021000021",
"payment_rail": "wire",
"account_owner_type": "business",
"bank_name": "JPMorgan Chase Bank",
"internal_account_id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
"address": {
"address_line1": "200 Park Avenue",
"city": "New York",
"state": "NY",
"postal_code": "10166",
"country": "US"
}
}
'
const response = await fetch("https://api.gravv.xyz/v1/external-accounts", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "bfd14696-c472-43f8-88ce-960042cf9499",
},
body: JSON.stringify({
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"payee_type": "ach_wire",
"currency": "USD",
"account_name": "Lakeside Title LLC",
"account_number": "4400918273",
"routing_number": "021000021",
"payment_rail": "wire",
"account_owner_type": "business",
"bank_name": "JPMorgan Chase Bank",
"internal_account_id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
"address": {
"address_line1": "200 Park Avenue",
"city": "New York",
"state": "NY",
"postal_code": "10166",
"country": "US"
}
}),
});
console.log(response.status, await response.json());
import os
import requests
response = requests.post(
"https://api.gravv.xyz/v1/external-accounts",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "bfd14696-c472-43f8-88ce-960042cf9499",
},
json={
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"payee_type": "ach_wire",
"currency": "USD",
"account_name": "Lakeside Title LLC",
"account_number": "4400918273",
"routing_number": "021000021",
"payment_rail": "wire",
"account_owner_type": "business",
"bank_name": "JPMorgan Chase Bank",
"internal_account_id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
"address": {
"address_line1": "200 Park Avenue",
"city": "New York",
"state": "NY",
"postal_code": "10166",
"country": "US",
},
},
)
print(response.status_code, response.json())
{
"data": {
"account_name": "LAKESIDE TITLE LLC",
"account_number": "4400918273",
"account_owner_type": "business",
"address": {
"address_line1": "200 Park Avenue",
"city": "New York",
"country": "US",
"postal_code": "10166",
"state": "NY"
},
"currency": "USD",
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"date_created": "2026-10-13T22:40:02.118230Z",
"id": "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b",
"institution_name": "JPMorgan Chase Bank",
"payee_type": "ach_wire",
"routing_number": "021000021",
"status": "pending"
},
"error": null
}
2. Wait until the payee is active
Listen for external_account.active, or poll GET /v1/external-accounts/6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b.
3. Send the wire
payment_rail_reference is the note for the receiving bank. It can be up to 35 characters of letters, digits, spaces and / - ? : ( ) . , ' +. On payouts from wallet-backed accounts, Gravv passes the note to the bank.
On payouts from a bank-held account, like the one here, Gravv checks the note but does not send it. Gravv sends description to the bank as the purpose of payment instead, so write what the money is for.
curl --request POST \
--url https://api.gravv.xyz/v1/transfer \
--header 'Api-Key: <API_KEY>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: 1bef266a-7450-4dd7-90fe-b6cad155a5b4' \
--data '
{
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"source": {
"source_type": "internal_account",
"id": "c56a4180-65aa-42ec-a945-5fd21dec0538"
},
"destination": {
"destination_type": "external_account",
"id": "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b",
"rail": "wire"
},
"amount": "48000.00",
"description": "Closing funds 12 Elm St escrow 5521",
"client_reference": "closing-5521",
"payment_rail_reference": "Escrow 5521 / 12 Elm St"
}
'
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": "1bef266a-7450-4dd7-90fe-b6cad155a5b4",
},
body: JSON.stringify({
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"source": {
"source_type": "internal_account",
"id": "c56a4180-65aa-42ec-a945-5fd21dec0538"
},
"destination": {
"destination_type": "external_account",
"id": "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b",
"rail": "wire"
},
"amount": "48000.00",
"description": "Closing funds 12 Elm St escrow 5521",
"client_reference": "closing-5521",
"payment_rail_reference": "Escrow 5521 / 12 Elm St"
}),
});
console.log(response.status, await response.json());
import os
import requests
response = requests.post(
"https://api.gravv.xyz/v1/transfer",
headers={
"Api-Key": os.environ["GRAVV_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "1bef266a-7450-4dd7-90fe-b6cad155a5b4",
},
json={
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"source": {
"source_type": "internal_account",
"id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
},
"destination": {
"destination_type": "external_account",
"id": "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b",
"rail": "wire",
},
"amount": "48000.00",
"description": "Closing funds 12 Elm St escrow 5521",
"client_reference": "closing-5521",
"payment_rail_reference": "Escrow 5521 / 12 Elm St",
},
)
print(response.status_code, response.json())
{
"data": {
"amount": "48000",
"amount_currency": "USD",
"reference": "5a9c3e1f-7b2d-4f6a-8c0e-2d4f6a8b0c12",
"date_created": "2026-10-13T14:30:05.204Z",
"transfer_status": "pending",
"transaction_id": "7e1a3c5b-9d2f-4a6c-8e0b-3f5a7c9e1b23",
"local_amount": null,
"local_currency": null,
"fee": "15",
"fee_currency": "USD",
"requires_approval": false
},
"error": null
}
4. Follow it to the end
Read GET /v1/transactions/7e1a3c5b-9d2f-4a6c-8e0b-3f5a7c9e1b23 (it shows transaction_type bank_account and channel wire), or wait for transfer.status.completed.
What can go wrong
| Response | Why | Fix |
|---|---|---|
400 routing number <routing_number> cannot receive wires; use the bank's wire routing number: invalid input |
The wire payee has an ACH-only routing number | Ask the payee for the wire routing number, or save the payee for ACH. Use a new Idempotency-Key |
400 payment_rail_reference for wire must be at most 35 characters: letters, digits, spaces and / - ? : ( ) . , ' + |
The message is too long, or has characters such as #, & or _ |
Shorten it. Write No instead of # |
400 this payee is registered for ach transfers, not wire — initiate on rail "ach", or add a payee on the wire rail |
You saved the payee for ACH | Save a wire payee for the same account. See two rails |
400 insufficient balance |
The amount plus the fee is more than the balance | Fund the account first |
Webhook transfer.status.failed with event_data.transfer_status manual_processing_required, transaction still pending |
The bank's response was unclear, so a person checks it before Gravv retries anything. This prevents a double payment | Do not send it again. Wait for the next webhook, or contact support with the reference |
| Every payee and transfer error in the ACH recipe also applies to wires |
Webhooks you will receive
You receive external_account.created, then external_account.active, then several transfer.status.pending events, then transfer.status.completed. See Transfer events.
Testing in sandbox
Test as for ACH. Fund the account with POST /v2/accounts/simulate/deposit and "rail": "wire". The routing number 021000021 passes the wire directory check in sandbox, and the payee becomes active in a few seconds.