---
title: "Pay a euro account by SEPA"
description: "Check an IBAN, save a SEPA payee and send a EUR payout from a wallet-backed account, converted at the current rate."
---
Use this recipe to check an IBAN, save a SEPA payee and send a EUR payout from a wallet-backed account. Gravv converts the amount at the current rate.

**Who it is for.** A US software company that pays a freelance designer's IBAN in Germany.

**Status.** Available from wallet-backed accounts to the 33 SEPA countries, if Gravv has enabled SEPA payouts for your business. Not supported from bank-held USD accounts.

## Money example

1. The company's wallet-backed USD account holds 5,000.00 USD.
2. The company sends 1,000.00 USD to Lena Fischer's IBAN by SEPA.
3. At an example rate of 0.885 EUR per USD, the response shows 885.00 EUR for Lena in `local_amount`. Gravv debits the account 1,000.00 USD plus the fee on your plan.

## Before you start

- A **wallet-backed** source account that is `active` and funded. Bank-held USD accounts cannot pay by SEPA.
- SEPA payouts enabled for your business. If the first transfer returns `transfers to DE/EUR are not supported at this time`, contact support.
- The recipient's IBAN, full name and address, and whether it is a person or a company.

## Find out what is required

- **Payee rules.** Set `payee_type` to `sepa` and `currency` to `EUR`, the only currency allowed. Send `iban`, `account_owner_type`, `bank_name` and `address`. Gravv verifies the IBAN's check digits. `bic` is optional, and Gravv checks its format when you send it. The IBAN's country must be a SEPA country, and Gravv takes the payee's country from the IBAN.
- **IBAN format.** [`POST /v1/external-accounts/verify`](/api-reference/external-accounts/post-v1-external-accounts-verify) checks the format and returns `verification_source` `local`. It does **not** confirm who holds the account.
- **Indicative rate.** Call [`GET /v1/transfer/rates?currency=EUR`](/api-reference/transfer/get-v1-transfer-rates) and read the item with `direction` `USD_EUR`. Match on `direction`, not on `currency_pair`. The rate is not locked. Gravv prices the transfer when you send it, and `local_amount` in the transfer response is the amount priced for the recipient.

## Steps

### 1. Check the IBAN (optional)

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/external-accounts/verify \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --data '
{
  "iban": "DE89370400440532013000"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/external-accounts/verify", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "iban": "DE89370400440532013000"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/external-accounts/verify",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
    },
    json={
        "iban": "DE89370400440532013000",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "verified": true,
    "verification_source": "local"
  },
  "error": null
}
```

If a check digit is wrong, the response has `verified: false` and a `message`, such as `IBAN checksum invalid (mod 97 = 28, expected 1)`.

### 2. Save the SEPA payee

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/external-accounts \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: fc5b1691-8679-46d8-b1bf-ae8a8109cc1a' \
     --data '
{
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "payee_type": "sepa",
  "currency": "EUR",
  "account_name": "Lena Fischer",
  "iban": "DE89370400440532013000",
  "bic": "COBADEFFXXX",
  "account_owner_type": "individual",
  "bank_name": "Commerzbank",
  "address": {
    "address_line1": "Torstrasse 101",
    "city": "Berlin",
    "postal_code": "10119",
    "country": "DE"
  }
}
'
```
```javascript title="Node"
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": "fc5b1691-8679-46d8-b1bf-ae8a8109cc1a",
  },
  body: JSON.stringify({
    "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "payee_type": "sepa",
    "currency": "EUR",
    "account_name": "Lena Fischer",
    "iban": "DE89370400440532013000",
    "bic": "COBADEFFXXX",
    "account_owner_type": "individual",
    "bank_name": "Commerzbank",
    "address": {
      "address_line1": "Torstrasse 101",
      "city": "Berlin",
      "postal_code": "10119",
      "country": "DE"
    }
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
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": "fc5b1691-8679-46d8-b1bf-ae8a8109cc1a",
    },
    json={
        "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "payee_type": "sepa",
        "currency": "EUR",
        "account_name": "Lena Fischer",
        "iban": "DE89370400440532013000",
        "bic": "COBADEFFXXX",
        "account_owner_type": "individual",
        "bank_name": "Commerzbank",
        "address": {
            "address_line1": "Torstrasse 101",
            "city": "Berlin",
            "postal_code": "10119",
            "country": "DE",
        },
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response (HTTP 202)"
{
  "data": {
    "account_name": "LENA FISCHER",
    "account_owner_type": "individual",
    "address": {
      "address_line1": "Torstrasse 101",
      "city": "Berlin",
      "country": "DE",
      "postal_code": "10119"
    },
    "bic": "COBADEFFXXX",
    "currency": "EUR",
    "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "date_created": "2026-10-13T22:41:24.032709Z",
    "iban": "DE89370400440532013000",
    "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "institution_country_iso_code": "DE",
    "institution_name": "Commerzbank",
    "payee_type": "sepa",
    "status": "pending"
  },
  "error": null
}
```

The payee starts as `pending`. Wait for the `external_account.active` webhook before you send the payout.

### 3. Send the payout

Send `amount` in the **source** account's currency.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: 0f1a9b6f-5e85-427f-b824-5cb46e698d30' \
     --data '
{
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "source": {
    "source_type": "internal_account",
    "id": "9b2d6f0e-3c1a-4e8b-a7d5-2f4c6e8a0b13"
  },
  "destination": {
    "destination_type": "external_account",
    "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
    "rail": "sepa"
  },
  "amount": "1000.00",
  "description": "Design work October",
  "client_reference": "sepa-lf-1013"
}
'
```
```javascript title="Node"
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": "0f1a9b6f-5e85-427f-b824-5cb46e698d30",
  },
  body: JSON.stringify({
    "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "source": {
      "source_type": "internal_account",
      "id": "9b2d6f0e-3c1a-4e8b-a7d5-2f4c6e8a0b13"
    },
    "destination": {
      "destination_type": "external_account",
      "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
      "rail": "sepa"
    },
    "amount": "1000.00",
    "description": "Design work October",
    "client_reference": "sepa-lf-1013"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
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": "0f1a9b6f-5e85-427f-b824-5cb46e698d30",
    },
    json={
        "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "source": {
            "source_type": "internal_account",
            "id": "9b2d6f0e-3c1a-4e8b-a7d5-2f4c6e8a0b13",
        },
        "destination": {
            "destination_type": "external_account",
            "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
            "rail": "sepa",
        },
        "amount": "1000.00",
        "description": "Design work October",
        "client_reference": "sepa-lf-1013",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "amount": "1000",
    "amount_currency": "USD",
    "reference": "4b7e9d2a-1c3f-4a5b-9e8d-7c6b5a4f3e21",
    "date_created": "2026-10-13T11:00:00.000Z",
    "transfer_status": "pending",
    "transaction_id": "2f9a8b7c-6d5e-4f3a-8b2c-1d0e9f8a7b6c",
    "local_amount": "885",
    "local_currency": "EUR",
    "fee": "2",
    "fee_currency": "USD",
    "requires_approval": false
  },
  "error": null
}
```

`local_amount` and `local_currency` are the amount priced for Lena when Gravv accepted the transfer. If a rate looks wrong (for example exactly 1), stop and contact support before you send more.

### 4. Follow it to the end

The transaction shows `transaction_type` `global_remittance` and `channel` `sepa`. The webhooks carry `workflow_type` `sepa_transfer`.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `IBAN checksum invalid (mod 97 = N, expected 1): invalid input` or `invalid IBAN format` | The IBAN has a typo | Check the IBAN again |
| 400 `iban required but missing for sepa payee: invalid input` | The request has no `iban` | Add `iban` |
| 400 `USD not supported for sepa external accounts: invalid input` | SEPA payees are EUR only | Send `"currency": "EUR"` |
| 400 `required but missing for swift payee: …` | The IBAN is outside SEPA and the request had no `payee_type`, so Gravv treated the payee as SWIFT | To pay USD abroad, use [SWIFT](/recipes/bank-payouts/pay-a-supplier-abroad-by-swift). Gravv does not support EUR outside SEPA |
| 400 `this account can only send USD: the recipient must receive USD` | The source is a bank-held USD account | Pay from a wallet-backed account |
| 400 `the selected source account cannot be used for an international transfer` | The source account cannot pay abroad | Contact support |
| 400 `transfers to DE/EUR are not supported at this time` | Gravv has not enabled that country for your business | Contact support |
| 400 `transfer rail "sepa" is not configured` | SEPA is not set up in this environment | Contact support |
| 400 `FX rate unavailable for currency conversion` | No rate is available right now | Retry later with a **new** key. Gravv replays the 400 for 24 hours under the old key |
| `external_account.setup_failed` with `error_message` `these account details could not be accepted` | The details were refused | [Fix the payee](/recipes/bank-payouts/fix-a-payee-that-failed-setup) |
| `transfer.status.failed` with `event_data.transfer_status` `manual_processing_required` | A person is checking the payout | Do not send it again. Contact support with the `reference` |

## Webhooks you will receive

You receive `external_account.created`, then `external_account.active`, then several `transfer.status.pending` events, then `transfer.status.completed`. Transfer webhooks carry no currency field, so read the amount and currency from the transaction.

## Testing in sandbox

Sandbox simulates SEPA payee setup, and the payee becomes `active` within seconds. `verify` returns `verified: true` for `DE89370400440532013000` and `verified: false` for `DE89370400440532013001`. Sandbox may not offer a wallet-backed source account for every currency. Where it does, fund one with [`POST /v2/accounts/simulate/deposit`](/api-reference/accounts-v2/post-v2-accounts-simulate-deposit).

## Reference

- [Verify an external account](/api-reference/external-accounts/post-v1-external-accounts-verify)
- [Get rates](/api-reference/transfer/get-v1-transfer-rates)
- [Add an external account](/api-reference/external-accounts/post-v1-external-accounts)
- [Create a transfer](/api-reference/transfer/post-v1-transfer)
