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

# Local rails — server-to-server

> Mint deposit instructions directly from your backend. No redirect required.

<Warning>
  Enable local rails for the currencies you want to accept in your Axra dashboard before calling this endpoint.
</Warning>

## Create a collection

`POST /business/collections`

Create a collection and return deposit instructions for the customer. Amounts are in major local currency units (for example, `10000` NGN).

`customer.name` and `customer.email` are optional. Both are stored on the collection record for your own reconciliation and shown in your Axra dashboard; they are not forwarded to the underlying payment provider. `customer.phone` is required for mobile money — it is forwarded to the provider so the deposit prompt reaches the right number.

### Bank transfer (NGN)

```bash theme={null}
curl -X POST https://api.useaxra.com/api/v1/business/collections \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "currency": "ngn",
    "country": "NG",
    "customer": {
      "name": "Adaobi Okeke",
      "email": "adaobi@example.com"
    },
    "channel": { "rail": "bank" }
  }'
```

```json Response theme={null}
{
  "id": "lcoll_01HVXYZ123456",
  "object": "collection",
  "status": "pending",
  "amount": 10000,
  "currency": "ngn",
  "country": "NG",
  "expiresAt": "2026-05-11T12:34:56Z",
  "channel": { "rail": "bank", "provider": "PAGA", "channelId": "ch_01HVNG7BANK" },
  "instructions": {
    "kind": "bank_transfer",
    "bankName": "Indulge MFB",
    "accountNumber": "9906154084",
    "accountName": "AXRA / Your Business",
    "reference": null
  },
  "metadata": null,
  "businessPaymentId": "bp_01HVXYZ123456",
  "createdAt": "2026-05-11T12:24:56Z"
}
```

### Mobile money (KES — M-PESA)

`customer.phone` is required for MoMo collections.

```bash theme={null}
curl -X POST https://api.useaxra.com/api/v1/business/collections \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "currency": "kes",
    "country": "KE",
    "customer": {
      "name": "James Kamau",
      "email": "james@example.com",
      "phone": "+254712345678"
    },
    "channel": { "rail": "momo" }
  }'
```

```json Response theme={null}
{
  "id": "lcoll_01HVXYZ789012",
  "object": "collection",
  "status": "pending",
  "amount": 5000,
  "currency": "kes",
  "country": "KE",
  "expiresAt": "2026-05-11T12:44:56Z",
  "channel": { "rail": "momo", "provider": "M-PESA", "channelId": "ch_01HVKE7MOMO" },
  "instructions": {
    "kind": "momo_prompt",
    "phone": "+254712345678",
    "prompt": "Approve the payment prompt from your mobile money provider."
  },
  "metadata": null,
  "businessPaymentId": "bp_01HVXYZ789012",
  "createdAt": "2026-05-11T12:34:56Z"
}
```

### Instant EFT (ZAR — Ozow)

For hosted-page rails, redirect the customer to `instructions.url` or pass `successUrl` when creating the collection.

```bash theme={null}
curl -X POST https://api.useaxra.com/api/v1/business/collections \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2500,
    "currency": "zar",
    "country": "ZA",
    "customer": {
      "name": "Thabo Mokoena",
      "email": "thabo@example.com"
    },
    "channel": { "rail": "eft" },
    "successUrl": "https://yourapp.com/orders/ord_123/success"
  }'
```

```json Response theme={null}
{
  "id": "lcoll_01HVXYZ345678",
  "object": "collection",
  "status": "pending",
  "amount": 2500,
  "currency": "zar",
  "country": "ZA",
  "expiresAt": "2026-05-11T12:49:56Z",
  "channel": { "rail": "eft", "provider": "Ozow", "channelId": "ch_01HVZA7EFT" },
  "instructions": {
    "kind": "hosted_page",
    "url": "https://pay.ozow.com/...",
    "provider": "Ozow"
  },
  "metadata": null,
  "businessPaymentId": "bp_01HVXYZ345678",
  "createdAt": "2026-05-11T12:39:56Z"
}
```

## Handle the completion webhook

Fulfill orders when Axra delivers `collection.completed`, not when you return deposit instructions to the customer.

1. Subscribe to `collection.completed` on your webhook endpoint (or include it in the endpoint event list).
2. Verify `X-Axra-Signature` against the raw request body using your webhook secret. See [Webhooks](/manage/webhooks).
3. Read `data.object` — the payload mirrors `GET /business/collections/{id}`.
4. Mark the order paid and release goods or services only after `status` is `completed`.

```javascript theme={null}
app.post('/webhooks/axra', (req, res) => {
  const signature = req.headers['x-axra-signature'];
  if (!verifyWebhookSignature(req.rawBody, signature, process.env.AXRA_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  const { event, data } = req.body;
  if (event === 'collection.completed' && data.object.status === 'completed') {
    fulfillOrder(data.object.metadata?.orderId, data.object.id);
  }

  res.status(200).send('OK');
});
```

## Channel discovery

List channels before creating a collection when your UI needs to show available rails dynamically, or to discover the per-channel minimum and maximum amounts.

```bash theme={null}
curl "https://api.useaxra.com/api/v1/business/local-rails/channels?country=NG&currency=ngn&amount=10000" \
  -H "x-api-key: YOUR_API_KEY"
```

Each channel in the response includes `min` and `max` in the local currency's major units. `min: 0` means no lower bound; `max: 0` means no upper bound. Filter or sort on these in your UI so customers don't see rails their amount doesn't qualify for.

```json theme={null}
{
  "channels": [
    { "rail": "bank", "provider": "Paystack", "providerHint": "PAYSTACK", "min": 100, "max": 0 },
    { "rail": "momo", "provider": "MTN Uganda", "providerHint": "MTN_UGANDA", "min": 15000, "max": 0 }
  ]
}
```

### Minimums and maximums

Local-rail channels have provider-side minimum (and sometimes maximum) amounts. If you call `POST /business/collections` with an `amount` that doesn't fit any channel for the requested `rail` and `country/currency`, you get a `400` with the actual minimum surfaced:

```json theme={null}
{
  "success": false,
  "error": {
    "code": "Amount 5000 UGX does not fit any momo channel for UG (minimum is 15000 UGX). Call GET /v1/business/local-rails/channels?country=UG&currency=ugx&amount=5000 to see what's available.",
    "statusCode": 400
  }
}
```

These limits are set by our settlement partners per corridor and change without notice — always read them from `/business/local-rails/channels` rather than hard-coding values in your client.

## Idempotency

Pass `idempotencyKey` on create to dedupe retries within one hour. A duplicate key returns the original collection instead of minting a second virtual account.

## Polling

Webhooks are the source of truth. If you must poll, call `GET /business/collections/{id}` about every five seconds while `status` is `pending` or `processing`. Use `GET /business/collections/{id}/refresh` to force a provider resync before giving up.

## Status reference

| Status       | Meaning                                                                           |
| ------------ | --------------------------------------------------------------------------------- |
| `pending`    | Deposit instructions are active; waiting for customer payment.                    |
| `processing` | Inbound funds detected; settlement is in progress.                                |
| `completed`  | Collection settled; merchant USDC wallet credited.                                |
| `failed`     | Collection failed (amount mismatch, provider rejection, or other terminal error). |
| `expired`    | Instructions expired before payment arrived.                                      |

## Error codes

| HTTP  | Code                      | When                                                                                                                                                                                        |
| ----- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `currency_unsupported`    | Currency or country is not enabled for your business, or the requested currency is not yet live (for example GHS).                                                                          |
| `400` | `customer_phone_required` | `channel.rail` is `momo` but `customer.phone` is missing.                                                                                                                                   |
| `400` | —                         | Amount is below the channel minimum or above the maximum for the requested rail. The error message includes the actual min/max. Discover both upfront via `/business/local-rails/channels`. |
| `400` | —                         | The requested rail is not available for the given country/currency. The error message lists which rails ARE available.                                                                      |
| `404` | —                         | Collection id does not exist for your business.                                                                                                                                             |

<Card title="API Reference" icon="code" href="/api-reference/local-rails/create-collection">
  Full endpoint documentation.
</Card>
