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

  1. The customer's USD account holds 60,000.00 USD.
  2. The customer wires 48,000.00 USD to Lakeside Title LLC. In this example, the fee on your plan is 15.00 USD.
  3. 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.

Reference

Was this page helpful?