> ## Documentation Index
> Fetch the complete documentation index at: https://docs.afriex.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connecting the Pieces

> How Customers, Payment Methods, and Transactions link together

## What this guide covers

If you've read the quickstart but aren't sure exactly *which ID goes where*, or why you need to create three different things before money can move, this guide is for you.

The Afriex Business API has three core building blocks. They must be created in order, and each one produces an ID that the next step needs. This guide explains what each building block is, what ID it gives you, and exactly where that ID is used.

***

## The big picture

Think of it like this: a **Customer** is the person your business is acting on behalf of (the sender or originator). A **Payment Method** holds the account details of the person being paid (the recipient). A **Transaction** is the instruction to move money, and it needs to know *on whose behalf* you are acting and *which recipient account* to use.

Here is how the IDs connect:

```mermaid theme={null}
flowchart TD
    A["<b>Step 1: Create Customer</b><br/><br/>POST /api/v1/customer<br/><br/>─────────────────────────────<br/><br/>✦ Returns: customerId"]
    B["<b>Step 2: Create Payment Method</b><br/><br/>POST /api/v1/payment-method<br/><br/>─────────────────────────────<br/><br/>↳ Input:  customerId<br/>✦ Returns: paymentMethodId"]
    C["<b>Step 3: Create Transaction</b><br/><br/>POST /api/v1/transaction<br/><br/>─────────────────────────────<br/><br/>↳ Input: customerId<br/>↳ Input: destinationId = paymentMethodId&nbsp;&nbsp;(WITHDRAW)<br/>↳ Input: sourceId &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;= paymentMethodId&nbsp;&nbsp;(DEPOSIT)<br/><br/>✦ Returns: transactionId"]

    A -->|"customerId"| B
    B -->|"paymentMethodId"| C
    A -->|"customerId"| C
```

### ID quick-reference

| ID field          | Belongs to        | Used in                                       |
| ----------------- | ----------------- | --------------------------------------------- |
| `customerId`      | Customer          | Payment Method creation, Transaction creation |
| `paymentMethodId` | Payment Method    | Transaction as `destinationId` or `sourceId`  |
| `destinationId`   | Transaction field | Equals a `paymentMethodId` (used in WITHDRAW) |
| `sourceId`        | Transaction field | Equals a `paymentMethodId` (used in DEPOSIT)  |
| `transactionId`   | Transaction       | Status lookups, webhook events                |

<Warning>
  `destinationId` and `sourceId` in a Transaction are both **Payment Method IDs**. They are not special IDs. They are exactly the `paymentMethodId` value returned when you created the Payment Method. This is the single most common point of confusion.
</Warning>

***

## Each building block explained

### Customer

**What it is:** A Customer is the person or business your business is acting on behalf of. They are the originator or sender of the transaction, not the recipient. For example, if your platform processes payroll for a company, each employee whose salary you send out is a Customer. The Customer record stores their identity information (name, email, phone, country) and is used to attribute transactions to the right person in your system.

**What ID it gives you:** `customerId`

**When you use that ID:**

* Pass it as `customerId` when creating a Payment Method (to link the account to this person)
* Pass it as `customerId` when creating a Transaction (to identify whose account is involved)

**Fields to create a Customer:**

| Field         | Required | Example                          |
| ------------- | -------- | -------------------------------- |
| `fullName`    | Yes      | `"Amara Osei"`                   |
| `email`       | Yes      | `"amara@example.com"`            |
| `phone`       | Yes      | `"+233201234567"` (E.164 format) |
| `countryCode` | Yes      | `"GH"` (ISO 3166-1 alpha-2)      |

***

### Payment Method

**What it is:** A Payment Method holds the account details of the person being paid or collected from: their bank account, mobile money wallet, or other financial account. It is linked to a Customer (the originator on whose behalf you are acting), but the account details it contains belong to the end recipient. You need a Payment Method before you can send or receive money, because the Transaction needs to know exactly *which account* to credit or debit.

**What ID it gives you:** `paymentMethodId`

**When you use that ID:**

* As `destinationId` in a WITHDRAW transaction (the account the money is sent to)
* As `sourceId` in a DEPOSIT transaction (the account the money is pulled from)

<Info>
  A Payment Method has a `type` field, either `WITHDRAW` (used to send funds to a recipient) or `DEPOSIT` (used to collect funds from a customer). Make sure you create the right type for the transaction you plan to run.
</Info>

**Supported channels:**

| Channel                | Description                   | Supports           | Common use case                             |
| ---------------------- | ----------------------------- | ------------------ | ------------------------------------------- |
| `BANK_ACCOUNT`         | Local bank transfer           | Withdraw           | Payout to Nigerian GTBank, Kenyan KCB       |
| `MOBILE_MONEY`         | Mobile wallet                 | Deposit & Withdraw | MTN MoMo, M-Pesa, Airtel Money              |
| `SWIFT`                | International wire transfer   | Withdraw           | Cross-border payouts to Europe, US          |
| `UPI`                  | Unified Payments Interface    | Withdraw           | Payouts to India                            |
| `INTERAC`              | Interac e-Transfer            | Deposit & Withdraw | Collections and payouts in Canada           |
| `WE_CHAT`              | WeChat Pay                    | Withdraw           | Payouts to China                            |
| `ALIPAY`               | Alipay                        | Withdraw           | Payouts to China                            |
| `VIRTUAL_BANK_ACCOUNT` | Afriex-issued virtual account | Deposit            | Receive collections (production only)       |
| `POOL_ACCOUNT`         | Shared pool account           | Deposit            | Shared collection account (production only) |
| `CRYPTO_WALLET`        | Crypto wallet                 | Deposit            | USDC / USDT deposits                        |

#### Virtual bank accounts

`VIRTUAL_BANK_ACCOUNT` is not created through the standard `POST /api/v1/payment-method` endpoint. Instead, use the dedicated `GET /api/v1/payment-method/virtual-account` endpoint, which retrieves an existing account or creates a new one automatically. This endpoint is **production only** and does not work on staging.

**VIRTUAL\_ACCOUNT** is the default type and has two sub-types, determined by whether you pass an `amount`:

* **Reserved (no `amount`):** A permanent virtual bank account dedicated to the customer. The same account number is returned on every call for that customer and currency. The customer can fund it at any time for any amount. For NGN, the customer must have a BVN on file before a reserved account can be created.

* **Time-sensitive (with `amount`):** A dynamic account tied to a specific amount. It expires after a set window (the response includes `expiresInMinutes`). Use this when collecting a specific payment within a defined time. Does not require BVN.

|                    | Reserved          | Time-sensitive                           |
| ------------------ | ----------------- | ---------------------------------------- |
| Triggered by       | Omitting `amount` | Passing `amount`                         |
| Expires            | Never             | Yes (see `expiresInMinutes` in response) |
| Amount-specific    | No                | Yes                                      |
| BVN required (NGN) | Yes               | No                                       |

**POOL\_ACCOUNT** returns the business's shared pool account for a country. Requires `country`; pass `customerId` to scope the response to a specific end-user. Use the `reference` on the response to reconcile incoming deposits.

Virtual accounts are for your **end customers**. Link each one to a `customerId` so payments are attributed to the right person.

***

### Transaction

**What it is:** A Transaction is the actual instruction to move money. It references a Customer and (depending on the type) one of their Payment Methods.

**What ID it gives you:** `transactionId`

**When you use that ID:**

* To check the status: `GET /api/v1/transaction/{transactionId}`
* It appears in webhook events so you can match notifications to records in your system

**Three transaction types:**

| Type       | What it does                                                     | Payment method needed               |
| ---------- | ---------------------------------------------------------------- | ----------------------------------- |
| `WITHDRAW` | Send money *from* your Afriex wallet *to* a customer's account   | `destinationId` (= paymentMethodId) |
| `DEPOSIT`  | Pull money *from* a customer's account *into* your Afriex wallet | `sourceId` (= paymentMethodId)      |
| `SWAP`     | Convert currency *within* your Afriex wallet                     | None                                |

<Note>
  **WITHDRAW:** Your business wallet is the implicit source. Afriex debits your wallet automatically. You do not specify a source payment method.

  **DEPOSIT:** Your business wallet is the implicit destination. Afriex credits your wallet automatically. You do not specify a destination payment method.

  **SWAP:** Both source and destination are your business wallet. No customer or payment method IDs are needed at all.
</Note>

***

## The 3-step integration sequence

Here is the exact order of operations, with the IDs flowing from one step to the next.

<Steps>
  <Step title="Create the Customer">
    Register the person on whose behalf the transaction is being made (the originator or sender).

    ```bash theme={null}
    curl -X POST https://sandbox.api.afriex.com/api/v1/customer \
      -H "Content-Type: application/json" \
      -H "x-api-key: YOUR_API_KEY" \
      -d '{
        "fullName": "Amara Osei",
        "email": "amara@example.com",
        "phone": "+233201234567",
        "countryCode": "GH"
      }'
    ```

    Save the `customerId` from the response. You need it in the next step.

    ```json theme={null}
    {
      "data": {
        "customerId": "69516dd0464b2213bd74cfad",
        "name": "Amara Osei",
        "email": "amara@example.com",
        "phone": "+233201234567",
        "countryCode": "GH"
      }
    }
    ```
  </Step>

  <Step title="Create the Payment Method">
    Register the recipient's bank account or mobile wallet (the account the money will be sent to or collected from). Link it to the Customer from Step 1 by passing their `customerId`.

    First, look up the correct institution code for the customer's bank or mobile provider:

    ```bash theme={null}
    curl "https://sandbox.api.afriex.com/api/v1/payment-method/institution?channel=MOBILE_MONEY&countryCode=GH" \
      -H "x-api-key: YOUR_API_KEY"
    ```

    Then create the payment method using the `institutionCode` and `institutionName` from that response:

    ```bash theme={null}
    curl -X POST https://sandbox.api.afriex.com/api/v1/payment-method \
      -H "Content-Type: application/json" \
      -H "x-api-key: YOUR_API_KEY" \
      -d '{
        "customerId": "69516dd0464b2213bd74cfad",
        "type": "WITHDRAW",
        "channel": "MOBILE_MONEY",
        "accountName": "Amara Osei",
        "accountNumber": "0201234567",
        "countryCode": "GH",
        "institution": {
          "institutionCode": "MTN",
          "institutionName": "MTN"
        }
      }'
    ```

    Save the `paymentMethodId` from the response. This is what goes into `destinationId` (or `sourceId`) in your transaction.

    ```json theme={null}
    {
      "data": {
        "paymentMethodId": "690df3281c11eea59108fcaf",
        "customerId": "69516dd0464b2213bd74cfad",
        "channel": "MOBILE_MONEY",
        "accountName": "Amara Osei",
        "accountNumber": "0201234567",
        "countryCode": "GH"
      }
    }
    ```
  </Step>

  <Step title="Create the Transaction">
    Now create the transaction using both IDs from the previous steps.

    * `customerId` = the ID from Step 1
    * `destinationId` = the `paymentMethodId` from Step 2 (for a WITHDRAW)

    ```bash theme={null}
    curl -X POST https://sandbox.api.afriex.com/api/v1/transaction \
      -H "Content-Type: application/json" \
      -H "x-api-key: YOUR_API_KEY" \
      -d '{
        "customerId": "69516dd0464b2213bd74cfad",
        "type": "WITHDRAW",
        "sourceAmount": "10",
        "destinationAmount": "91.50",
        "sourceCurrency": "USD",
        "destinationCurrency": "GHS",
        "destinationId": "690df3281c11eea59108fcaf",
        "meta": {
          "idempotencyKey": "550e8400-e29b-41d4-a716-446655440001",
          "reference": "ORDER-98765"
        }
      }'
    ```

    The response includes a `transactionId`. Use it to track status or match incoming webhook events.
  </Step>
</Steps>

***

## Transaction type payloads side by side

<CodeGroup>
  ```json WITHDRAW: Pay out to a customer's account theme={null}
  {
    "customerId": "69516dd0464b2213bd74cfad",
    "type": "WITHDRAW",
    "sourceAmount": "10",
    "destinationAmount": "16500",
    "sourceCurrency": "USD",
    "destinationCurrency": "NGN",
    "destinationId": "690df3281c11eea59108fcaf",
    "meta": {
      "idempotencyKey": "550e8400-e29b-41d4-a716-446655440001",
      "reference": "ORDER-001"
    }
  }
  ```

  ```json DEPOSIT: Collect from a customer's account theme={null}
  {
    "customerId": "69516dd0464b2213bd74cfad",
    "type": "DEPOSIT",
    "sourceAmount": "500",
    "destinationAmount": "12",
    "sourceCurrency": "KES",
    "destinationCurrency": "USD",
    "sourceId": "690df3281c11eea59108fcc1",
    "meta": {
      "idempotencyKey": "550e8400-e29b-41d4-a716-446655440002",
      "reference": "COLLECTION-001"
    }
  }
  ```

  ```json SWAP: Convert currency in your wallet theme={null}
  {
    "type": "SWAP",
    "sourceAmount": "500",
    "sourceCurrency": "USD",
    "destinationCurrency": "NGN",
    "meta": {
      "idempotencyKey": "550e8400-e29b-41d4-a716-446655440003",
      "reference": "SWAP-001"
    }
  }
  ```
</CodeGroup>

### What changes between each type

| Field                 | WITHDRAW                     | DEPOSIT                      | SWAP                                      |
| --------------------- | ---------------------------- | ---------------------------- | ----------------------------------------- |
| `customerId`          | Required                     | Required                     | Not required                              |
| `destinationId`       | Required (= paymentMethodId) | Not used                     | Not used                                  |
| `sourceId`            | Not used                     | Required (= paymentMethodId) | Not used                                  |
| `destinationAmount`   | Required                     | Required                     | Optional (API calculates it at live rate) |
| `meta.idempotencyKey` | Required                     | Required                     | Required                                  |
| `meta.reference`      | Required                     | Required                     | Required                                  |

***

## Payment method fields by channel

Different payment channels require different fields. Use the sections below to find the exact payload shape for your channel.

<AccordionGroup>
  <Accordion title="BANK_ACCOUNT: Local bank transfer">
    Required fields: `customerId`, `channel`, `accountName`, `accountNumber`, `countryCode`, `institution.institutionCode`, `institution.institutionName`

    Look up valid institution codes first: [List Institutions](/api-reference/endpoint/payment-methods/institution)

    You can also verify the account holder name before creating: [Resolve Payment Method](/api-reference/endpoint/payment-methods/resolve)

    ```json theme={null}
    {
      "customerId": "69516dd0464b2213bd74cfad",
      "type": "WITHDRAW",
      "channel": "BANK_ACCOUNT",
      "accountName": "John Doe",
      "accountNumber": "0123456789",
      "countryCode": "NG",
      "institution": {
        "institutionCode": "058",
        "institutionName": "GTBank"
      }
    }
    ```
  </Accordion>

  <Accordion title="MOBILE_MONEY: M-Pesa, MTN MoMo, Airtel, etc.">
    Required fields: `customerId`, `channel`, `accountName`, `accountNumber` (the mobile number), `countryCode`, `institution.institutionCode`, `institution.institutionName`

    Look up valid institution codes for the country: [List Institutions](/api-reference/endpoint/payment-methods/institution)

    ```json theme={null}
    {
      "customerId": "69516dd0464b2213bd74cfad",
      "type": "WITHDRAW",
      "channel": "MOBILE_MONEY",
      "accountName": "Amara Osei",
      "accountNumber": "0201234567",
      "countryCode": "GH",
      "institution": {
        "institutionCode": "MTN",
        "institutionName": "MTN"
      }
    }
    ```
  </Accordion>

  <Accordion title="SWIFT: International wire transfer">
    Required fields: `customerId`, `channel`, `accountName`, `accountNumber` (IBAN or account number), `countryCode`, `institution.institutionCode` (SWIFT/BIC), `institution.institutionName`, `institution.institutionAddress`, `recipient.recipientAddress`

    Use [Institution Codes](/api-reference/endpoint/payment-methods/institution-codes) to resolve a SWIFT code to a bank name.

    ```json theme={null}
    {
      "customerId": "69516dd0464b2213bd74cfad",
      "type": "WITHDRAW",
      "channel": "SWIFT",
      "accountName": "John Doe",
      "accountNumber": "DE89370400440532013000",
      "countryCode": "DE",
      "institution": {
        "institutionCode": "DEUTDEDB",
        "institutionName": "Deutsche Bank",
        "institutionAddress": "Taunusanlage 12, 60262 Frankfurt am Main, Germany"
      },
      "recipient": {
        "recipientAddress": "Musterstrasse 1, 10115 Berlin, Germany"
      }
    }
    ```
  </Accordion>

  <Accordion title="UPI: India">
    Required fields: `customerId`, `channel`, `accountName`, `accountNumber` (UPI ID / VPA), `countryCode`, `institution.institutionCode` (`"UPI"`), `institution.institutionName` (`"UPI"`), `recipient.recipientPhone`

    ```json theme={null}
    {
      "customerId": "69516dd0464b2213bd74cfad",
      "type": "WITHDRAW",
      "channel": "UPI",
      "accountName": "Raj Kumar",
      "accountNumber": "rajkumar@upi",
      "countryCode": "IN",
      "institution": {
        "institutionCode": "UPI",
        "institutionName": "UPI"
      },
      "recipient": {
        "recipientPhone": "+919876543210"
      }
    }
    ```
  </Accordion>

  <Accordion title="INTERAC: Canada">
    Required fields: `customerId`, `channel`, `accountName`, `accountNumber` (the email address registered for Interac), `countryCode`, `institution.institutionCode` (`"INTERAC"`), `institution.institutionName` (`"INTERAC"`), `recipient.recipientEmail`

    ```json theme={null}
    {
      "customerId": "69516dd0464b2213bd74cfad",
      "type": "WITHDRAW",
      "channel": "INTERAC",
      "accountName": "John Doe",
      "accountNumber": "john.doe@email.com",
      "countryCode": "CA",
      "institution": {
        "institutionCode": "INTERAC",
        "institutionName": "INTERAC"
      },
      "recipient": {
        "recipientEmail": "john.doe@email.com"
      }
    }
    ```
  </Accordion>

  <Accordion title="WE_CHAT: China">
    Required fields: `customerId`, `channel`, `accountName`, `accountNumber` (phone number), `countryCode`, `institution.institutionCode` (`"WECHAT"`), `institution.institutionName` (`"WECHAT"`), `recipient.recipientPhone`

    ```json theme={null}
    {
      "customerId": "69516dd0464b2213bd74cfad",
      "type": "WITHDRAW",
      "channel": "WE_CHAT",
      "accountName": "Zhang Wei",
      "accountNumber": "+8613812345678",
      "countryCode": "CN",
      "institution": {
        "institutionCode": "WECHAT",
        "institutionName": "WECHAT"
      },
      "recipient": {
        "recipientPhone": "+8613812345678"
      }
    }
    ```
  </Accordion>

  <Accordion title="ALIPAY: China">
    Required fields: `customerId`, `channel`, `accountName`, `accountNumber` (Alipay-linked phone number or account ID), `countryCode`, `institution.institutionName`

    ```json theme={null}
    {
      "customerId": "69516dd0464b2213bd74cfad",
      "type": "WITHDRAW",
      "channel": "ALIPAY",
      "accountName": "Zhang Wei",
      "accountNumber": "+8613812345678",
      "countryCode": "CN",
      "institution": {
        "institutionName": "ALIPAY"
      }
    }
    ```
  </Accordion>

  <Accordion title="VIRTUAL_BANK_ACCOUNT: Receive deposits">
    Virtual bank accounts are not created through the standard `POST /api/v1/payment-method` endpoint. Use the dedicated endpoint instead:

    ```bash theme={null}
    GET /api/v1/payment-method/virtual-account
    ```

    This endpoint retrieves an existing virtual account for the customer or creates one automatically. It is **production only** and does not work on staging.

    **Two sub-types**, determined by whether you pass an `amount`:

    |                    | Reserved | Time-sensitive                          |
    | ------------------ | -------- | --------------------------------------- |
    | Pass `amount`?     | No       | Yes                                     |
    | Expires            | Never    | Yes, see `expiresInMinutes` in response |
    | Amount-specific    | No       | Yes                                     |
    | BVN required (NGN) | Yes      | No                                      |

    See the [Virtual Account endpoint](/api-reference/endpoint/payment-methods/virtual-account) for the full request and response reference.
  </Accordion>
</AccordionGroup>

***

## Institution fields: which one to use and when

The `institution` object has several fields that look similar. Here is exactly what each one is and when you need it.

| Field                | What it is                                                                                         | When you need it                                                                                   |
| -------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `institutionCode`    | Short identifier for the bank or provider (e.g. `"058"` for GTBank Nigeria, `"MTN"` for MTN Ghana) | **Always required when creating a Payment Method**                                                 |
| `institutionName`    | Human-readable display name (e.g. `"GTBank"`, `"MTN"`)                                             | **Always required when creating a Payment Method**                                                 |
| `institutionId`      | Afriex's internal database ID for the institution                                                  | **Output only. Never pass this as input.** It appears in responses but is not needed for creation. |
| `institutionAddress` | Physical address of the institution                                                                | **Only for SWIFT transfers.** Skip it for all other channels.                                      |

### How to find the right institutionCode

1. Call `GET /api/v1/payment-method/institution?channel=BANK_ACCOUNT&countryCode=NG`
2. The response returns a list of institutions, each with `institutionCode` and `institutionName`
3. Copy those values directly into your Payment Method creation request

For SWIFT and US routing numbers, use `GET /api/v1/payment-method/institution/codes` to resolve a known SWIFT/BIC code to a bank name.

<Tip>
  Cache the institution list per country and channel. It changes infrequently and there is no need to fetch it on every Payment Method creation.
</Tip>

***

## The meta object

Every transaction must include a `meta` object with two required fields. Without them, the API returns a `400` error.

```json theme={null}
"meta": {
  "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
  "reference": "ORDER-12345"
}
```

| Field            | Required | What it does                                                                                                                                                                                   |
| ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `idempotencyKey` | **Yes**  | A unique string you generate per transaction attempt. If you retry a failed request with the same key, Afriex returns the original transaction instead of creating a duplicate. Use a UUID v4. |
| `reference`      | **Yes**  | Your own internal ID for this transaction (e.g. order number, payment ID, or invoice reference). Used for reconciliation and returned on the transaction as `merchantReference`.               |
| `narration`      | No       | A human-readable description (e.g. `"Salary payment for March 2025"`).                                                                                                                         |
| `invoice`        | No       | A base64-encoded invoice document to attach to the transaction.                                                                                                                                |

<Warning>
  Never reuse an `idempotencyKey` across different transaction attempts. Use a fresh UUID for every new transaction. Reusing a key on a different transaction will cause Afriex to return the original transaction, not the new one.
</Warning>

***

## Common mistakes

### 1. Using a customerId as destinationId or sourceId

`destinationId` and `sourceId` must be a `paymentMethodId`, not a `customerId`. Both look like hex strings, which makes it easy to mix them up.

```json theme={null}
// Wrong: this is a customerId
"destinationId": "69516dd0464b2213bd74cfad"

// Correct: this is the paymentMethodId from Step 2
"destinationId": "690df3281c11eea59108fcaf"
```

***

### 2. Skipping the 3-step sequence

You cannot create a Transaction without first creating a Customer and a Payment Method. The API returns a `404` or `400` if either does not exist. Always follow: **Customer → Payment Method → Transaction**.

***

### 3. Mixing up DEPOSIT and WITHDRAW fields

* **WITHDRAW** uses `destinationId` (where the money goes)
* **DEPOSIT** uses `sourceId` (where the money comes from)

Passing `destinationId` on a DEPOSIT (or `sourceId` on a WITHDRAW) will cause the API to reject the request.

***

### 4. Assuming SWAP needs a payment method

SWAP does not touch any external account. It converts currency inside your Afriex business wallet. No `customerId`, `destinationId`, or `sourceId` is needed. Only the currency pair and the `meta` object are required.

```json theme={null}
{
  "type": "SWAP",
  "sourceAmount": "500",
  "sourceCurrency": "USD",
  "destinationCurrency": "NGN",
  "meta": {
    "idempotencyKey": "unique-uuid-here",
    "reference": "SWAP-001"
  }
}
```

***

### 5. Omitting meta.idempotencyKey or meta.reference

Both are required on every transaction. The API returns a `400` if either is missing. Always generate a fresh UUID for `idempotencyKey` and use your own internal order or payment ID as `reference`.

***

### 6. Hardcoding institution codes without looking them up

Do not guess institution codes. Use `GET /api/v1/payment-method/institution?channel=BANK_ACCOUNT&countryCode=NG` to get the correct code for each country and channel. The same bank can have a different code in different countries.

***

### 7. Passing institutionId as input

`institutionId` is an output field. It appears in API responses but you never need to supply it when creating a Payment Method. The fields you pass are `institutionCode` and `institutionName`.

***

## Tracking what happens after you create a transaction

Transactions do not complete instantly. After creation the status will be `PENDING` or `PROCESSING`. Here is how to know when it finishes.

<Note>
  **Testing in sandbox?** Sandbox transactions settle automatically within \~1-2 minutes, so you can test the full flow end to end. Set the transaction's `meta.reference` to a value containing `fail` to test a **FAILED** outcome, or any other value for **SUCCESS**. See [Testing transaction outcomes in sandbox](/development#testing-transaction-outcomes-in-sandbox).
</Note>

**Option A (recommended): Webhooks**

Subscribe to `TRANSACTION.UPDATED` events. When the transaction reaches a terminal status, Afriex sends an HTTP POST to your webhook URL with the full transaction payload.

See [Webhook setup](/api-reference/endpoint/webhooks/introduction) for configuration steps and payload examples.

**Option B: Poll the status endpoint**

```bash theme={null}
curl "https://sandbox.api.afriex.com/api/v1/transaction/TRANSACTION_ID" \
  -H "x-api-key: YOUR_API_KEY"
```

<Tip>
  Prefer webhooks over polling. Polling adds latency and uses API quota. Webhooks notify you the moment the status changes.
</Tip>

### Terminal statuses

| Status      | Meaning                                                                                                                                                         |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SUCCESS`   | Transaction finished successfully                                                                                                                               |
| `FAILED`    | Transaction could not be processed. Check `meta.failureReason.code` for a stable `AFX_*` reason (see [Failure reasons](/api-reference/models#failure-reasons)). |
| `CANCELLED` | Cancelled before processing started                                                                                                                             |
| `REFUNDED`  | Funds returned to the sender                                                                                                                                    |
| `REJECTED`  | Rejected after manual review                                                                                                                                    |

***

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="bolt" href="/quickstart">
    Make your first API call in under 5 minutes.
  </Card>

  <Card title="Create a Customer" icon="users" href="/api-reference/endpoint/customers/create">
    Full parameter reference for customer creation.
  </Card>

  <Card title="Create a Payment Method" icon="credit-card" href="/api-reference/endpoint/payment-methods/create">
    All channels and required fields in the API reference.
  </Card>

  <Card title="Create a Transaction" icon="money-bill-transfer" href="/api-reference/endpoint/transactions/create">
    Full transaction creation reference with all fields.
  </Card>

  <Card title="Data Model Reference" icon="table" href="/api-reference/models">
    Complete field-by-field documentation for all models.
  </Card>

  <Card title="Webhooks" icon="bell" href="/api-reference/endpoint/webhooks/introduction">
    Receive real-time transaction status updates.
  </Card>
</CardGroup>
