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

# Initiate Payment Collection

> Charge a mobile-money account to credit your company's pay-in wallet. Supports MTN, Orange, Moov, Wave, M-Pesa, and other African operators.

## Overview

`POST /payment/collect` initiates a **pay-in**: it charges a mobile-money account and credits the proceeds to your company's pay-in wallet for the matching country/currency. The call returns immediately with `status: PENDING`; the final outcome (`SUCCESS` or `FAILED`) reaches you asynchronously via webhook (or via `notify_url` if you supply one).

For some operators, the end user must approve the charge with an OTP or USSD step — see the country/operator matrix in [Payment guidelines](/api-reference/endpoint/payment_guide_lines).

## When to use it

* You need to charge an end user's mobile-money account to settle a bill, top up a balance, or fund a service.

For the reverse direction (sending money out to a mobile-money account), use [`POST /payment/payout`](/api-reference/endpoint/post-payment-payout).

## Prerequisites

* Your company has a wallet for the requested `(country, currency)` pair. If not, create it first via [`POST /wallets`](/api-reference/endpoint/post-wallets).
* The combination of `country`, `currency`, and `operator` is supported — see [Payment guidelines](/api-reference/endpoint/payment_guide_lines).

## Request

### Headers

| Name            | Required | Description             |
| --------------- | -------- | ----------------------- |
| `Authorization` | Yes      | `Bearer <access_token>` |
| `Content-Type`  | Yes      | `application/json`      |

### Body

| Field          | Type   | Required | Constraints / format                                                                                                                                                                                                                                |
| -------------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `operator`     | string | Yes      | One of `mtn`, `orange`, `moov`, `airtel`, `mpesa`, `afrimoney`, `vodacom`, `wave`, `wligdicash`, `expresso`, `free`, `tmoney`, `celtiis`, `coris`. Lowercase.                                                                                       |
| `country`      | string | Yes      | ISO 3166-1 alpha-2, **uppercase** (e.g. `CM`, `CI`, `SN`). Selects the wallet.                                                                                                                                                                      |
| `phone_number` | string | Yes      | Country code + local number, **no `+`** (e.g. `237670000000`). This format is specific to payments; other endpoints use E.164 with `+`.                                                                                                             |
| `amount`       | number | Yes      | Minimum `1`. Unit is the major currency unit of `currency`.                                                                                                                                                                                         |
| `currency`     | string | Yes      | ISO 4217. Must match a wallet you own. Common values: `XAF`, `XOF`, `CDF`, `GNF`, `USD`.                                                                                                                                                            |
| `external_id`  | string | No       | **Idempotency key.** Up to 255 chars. If you supply the same value twice, the second call is rejected with `400`. Auto-generated if omitted (you lose idempotency).                                                                                 |
| `notify_url`   | string | No       | HTTPS URL to receive the final status webhook. If omitted, only the company-level webhook is fired.                                                                                                                                                 |
| `reference_id` | string | No       | Free-form reference shown in the dashboard. Up to 255 chars.                                                                                                                                                                                        |
| `lang`         | string | No       | Two-letter language code for any operator-side prompts (e.g. `en`, `fr`).                                                                                                                                                                           |
| `purpose`      | string | No       | Free-form description of why the collection is happening (e.g. `"Invoice payment"`).                                                                                                                                                                |
| `otp_code`     | string | No       | One-time code the payer generates on their phone, **required by some operators** (notably **Orange Burkina Faso** and **Orange Senegal**) to validate the collection. Forwarded to the operator as `otp_code`. See the Orange Money workflow below. |

```json theme={null}
{
  "operator": "mtn",
  "country": "CM",
  "phone_number": "237670000000",
  "amount": 1000,
  "currency": "XAF",
  "external_id": "ORD-2026-001",
  "notify_url": "https://your-app.com/webhooks/cartevo",
  "purpose": "Invoice payment for May 2026"
}
```

### Orange Money workflow (Burkina Faso & Senegal)

**Orange Money in Burkina Faso and Senegal requires an OTP.** The operator does
**not** push a confirmation prompt to the payer — instead the payer must
**generate the one-time code themselves** and give it to you, then you submit it
with the collection:

1. The payer dials the Orange USSD code on their phone to generate the payment OTP:
   * **Burkina Faso (`country: "BF"`):** `*144*4*6*<amount>#` — where `<amount>` is the collection amount.
   * **Senegal (`country: "SN"`):** `#144*391#`
2. The payer gives you the generated OTP.
3. You call `POST /payment/collect` with `operator: "orange"`, the matching
   `country`, `currency: "XOF"`, and the OTP in **`otp_code`**.

```json theme={null}
{
  "operator": "orange",
  "country": "BF",
  "phone_number": "22670000000",
  "amount": 1000,
  "currency": "XOF",
  "otp_code": "123456",
  "external_id": "ORD-2026-002"
}
```

<Warning>
  The OTP is short-lived (a few minutes) and single-use. If it has expired the
  collection fails — the payer must generate a fresh one and you retry with a
  new `external_id`.
</Warning>

<Note>
  Other operators also require an OTP (e.g. **Wallet LigdiCash** in Burkina
  Faso). Whenever the operator requires one, send it in `otp_code`. See the
  [Payment guidelines](/api-reference/endpoint/payment_guide_lines) for the full
  country/operator OTP matrix.
</Note>

### Idempotency

Always supply your own `external_id`. Cartevo rejects duplicates with `400` (after the first call), so storing the value before you send the request and reusing it on retry is safe. If you let Cartevo auto-generate one, you lose this protection.

## Response

### 200 — Collection initiated

```json theme={null}
{
  "success": true,
  "statusCode": 200,
  "message": "Collection initiated successfully",
  "data": {
    "transaction_id": "550e8400-e29b-41d4-a716-446655440000",
    "external_id": "ORD-2026-001",
    "status": "PENDING",
    "amount": 1000,
    "currency": "XAF",
    "operator": "mtn",
    "country": "CM",
    "phone_number": "237670000000",
    "initiated_at": "2026-05-09T10:15:00.000Z",
    "provider_link": null
  }
}
```

| Field            | Type           | Description                                                                                                                                                                       |
| ---------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transaction_id` | string         | Cartevo transaction ID (UUID). Use this for `GET /payment/transactions/{id}/status`.                                                                                              |
| `external_id`    | string         | Echoes your idempotency key (or the auto-generated one).                                                                                                                          |
| `status`         | string         | `PENDING` immediately after this call. Other values: `PROCESSING`, `SUCCESS`, `FAILED`.                                                                                           |
| `initiated_at`   | string         | ISO 8601 (UTC).                                                                                                                                                                   |
| `provider_link`  | string \| null | **Link-based operators only (e.g. Wave CI/SN).** A payment URL you **must redirect the payer to** so they can confirm. `null` for operators that don't use a link (MTN, Orange…). |

### Wave workflow (payment link)

**Wave (Côte d'Ivoire, Senegal, …) requires a payment link.** Wave does not
push a USSD/OTP prompt; instead AfribaPay returns a **`provider_link`** in the
response and the payer must open it to confirm:

1. Call `POST /payment/collect` with `operator: "wave"`.
2. The response contains a non-null **`provider_link`** (e.g.
   `https://pay.wave.com/c/cos-...`).
3. **Redirect the payer to that URL** (or open it). They confirm the payment on
   Wave's page.
4. The transaction stays `PENDING` until they confirm; the final
   `SUCCESS`/`FAILED` arrives via webhook.

<Warning>
  The Wave link is short-lived. If the payer doesn't confirm in time the
  collection fails — retry with a new `external_id` to get a fresh link.
</Warning>

### Lifecycle

```
PENDING → PROCESSING → SUCCESS
                    ↘ FAILED
```

* **`PENDING`** — Cartevo accepted the request; the operator has been notified.
* **`PROCESSING`** — The operator is awaiting user action (OTP / USSD) or processing internally.
* **`SUCCESS`** — Funds have been credited to your pay-in wallet. The `payment.collect` webhook fires.
* **`FAILED`** — End user rejected, insufficient funds at the operator, OTP timeout, etc. See `error_message` in the status endpoint.

`PROCESSING` can last up to **15 minutes** for OTP-required operators. Beyond that, treat as failed and reconcile via the status endpoint.

### Error responses

| Status | `message` example                                                      | Trigger                                                                |
| ------ | ---------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `400`  | `"Validation failed: country must be a valid ISO 3166-1 alpha-2 code"` | Field-level validation failed.                                         |
| `400`  | `"Duplicate external_id"`                                              | An earlier call used the same `external_id`.                           |
| `400`  | `"No wallet for this country and currency"`                            | You don't own a wallet for the `(country, currency)` pair.             |
| `400`  | `"Operator not available for this country/currency"`                   | See [Payment guidelines](/api-reference/endpoint/payment_guide_lines). |
| `401`  | `"Unauthorized"`                                                       | Missing or expired token.                                              |

## Webhooks fired

* [`payment.collect`](/api-reference/endpoint/webhook#payment-collect) — fires when the collection is initiated, then again on status changes (`SUCCESS` / `FAILED`).
* If you supplied `notify_url`, it receives a copy of each status change.

## Code examples

```bash cURL theme={null}
curl -X POST https://api.cartevo.co/api/v1/payment/collect \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "operator": "mtn",
    "country": "CM",
    "phone_number": "237670000000",
    "amount": 1000,
    "currency": "XAF",
    "external_id": "ORD-2026-001"
  }'
```

```js Node.js (axios) theme={null}
const externalId = `ORD-${Date.now()}`;
await db.savePendingCollection(externalId);

const { data } = await axios.post(
  "https://api.cartevo.co/api/v1/payment/collect",
  {
    operator: "mtn",
    country: "CM",
    phone_number: "237670000000",
    amount: 1000,
    currency: "XAF",
    external_id: externalId,
    notify_url: "https://your-app.com/webhooks/cartevo",
  },
  { headers: { Authorization: `Bearer ${token}` } }
);
```

## Related

* [`GET /payment/transactions/{id}/status`](/api-reference/endpoint/get-payment-transaction-status) — poll for the outcome.
* [`POST /payment/payout`](/api-reference/endpoint/post-payment-payout) — send money out via mobile money.
* [Payment guidelines](/api-reference/endpoint/payment_guide_lines) — supported countries, operators, OTP/USSD requirements.
* [Webhooks](/api-reference/endpoint/webhook) — full webhook catalogue.


## OpenAPI

````yaml POST /payment/collect
openapi: 3.1.0
info:
  title: Cartevo API
  description: Essential endpoints for wallets, transactions, customers and conversion.
  version: 1.0.0
servers:
  - url: https://api.cartevo.co/api/v1
security:
  - bearerAuth: []
paths:
  /payment/collect:
    post:
      summary: Initiate a payment
      description: >-
        Initiate a payment  (payin/money collection). This endpoint validates
        company and wallet existence, creates a transaction record with
        INITIATED status, updates transaction to PENDING on success or FAILED on
        error, and returns transaction details.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                operator:
                  type: string
                  enum:
                    - mtn
                    - orange
                    - moov
                    - airtel
                    - mpesa
                    - afrimoney
                    - vodacom
                    - wave
                    - wligdicash
                    - expresso
                    - free
                    - tmoney
                    - celtiis
                    - coris
                  example: mtn
                country:
                  type: string
                  pattern: ^[A-Z]{2}$
                  example: CM
                phone_number:
                  type: string
                  example: '237670000000'
                amount:
                  type: number
                  minimum: 1
                  example: 1000
                currency:
                  type: string
                  example: XAF
                notify_url:
                  type: string
                  example: https://example.com/webhook/payment
                external_id:
                  type: string
                  description: >-
                    Idempotency key (up to 255 chars). Duplicate values are
                    rejected with 400 after the first call. Auto-generated if
                    omitted.
                reference_id:
                  type: string
                  description: >-
                    Free-form reference shown in the dashboard (up to 255
                    chars).
                lang:
                  type: string
                  description: >-
                    Two-letter language code for operator-side prompts (e.g. en,
                    fr).
                purpose:
                  type: string
                  description: Free-form description of the payment's purpose.
              required:
                - operator
                - country
                - phone_number
                - amount
                - currency
      responses:
        '200':
          description: Collection initiated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Collection initiated successfully
                  data:
                    $ref: '#/components/schemas/CollectionResponse'
        '400':
          description: Bad request - validation error or duplicate external_id
          content:
            application/json:
              schema:
                type: object
                properties:
                  statusCode:
                    type: integer
                    example: 400
                  message:
                    type: string
                    example: Transaction with external_id COLL-123 already exists
                  error:
                    type: string
                    example: Bad Request
        '401':
          description: Unauthorized - invalid or missing authentication token
components:
  schemas:
    CollectionResponse:
      type: object
      properties:
        transaction_id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        external_id:
          type: string
          example: COLL-1234567890-abc12345
        status:
          type: string
          enum:
            - PENDING
            - SUCCESS
            - FAILED
            - PROCESSING
          example: PENDING
        amount:
          type: number
          example: 1000
        currency:
          type: string
          example: XAF
        operator:
          type: string
          example: mtn
        country:
          type: string
          example: CM
        phone_number:
          type: string
          example: '237670000000'
        initiated_at:
          type: string
          format: date-time
          example: '2025-01-08T12:00:00.000Z'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````