---
updatedAt: 2026-06-05T15:47:06.000Z
agentTools:
  projectIndex: https://capera.readme.io/llms.txt
---

# Idempotency

Network failures happen. A request can time out after leaving your server but before you receive a response — leaving you unsure whether it was processed. Idempotency lets you safely retry such requests without the risk of creating duplicate transactions.

## How Capera handles idempotency

Capera uses the `reference` field as the idempotency key on all write operations. If you submit a request with a reference that already exists, Capera returns the original result rather than creating a new record.

This applies to:

| Endpoint                      | Idempotency key |
| ----------------------------- | --------------- |
| `POST /v1/transfers/initiate` | `reference`     |
| `POST /v1/momo/transfer`      | `reference`     |
| `POST /v1/momo/collection`    | `reference`     |

## The safe retry pattern

**1. Always check before retrying**

If a request times out or returns a `5xx` error, check whether it succeeded before retrying:

```bash
# Check if the transfer was created
curl https://api.withcapera.com/b2b/v1/transfers/TRF-2024-001 \
  -H "Authorization: Bearer $CAPERA_API_KEY"
```

* If the response returns the transfer → it was created. Do not retry.
* If you get `400` with "Transfer not found" → it was not created. Safe to retry with the same reference.

**2. Retry with the same reference**

Retrying with the same `reference` is safe — the original request will be returned:

```bash
curl -X POST https://api.withcapera.com/b2b/v1/transfers/initiate \
  -H "Authorization: Bearer $CAPERA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "TRF-2024-001",
    "amount": 500000,
    "bankSlug": "first-bank",
    "accountNumber": "1234567890",
    "accountName": "Amara Okafor"
  }'
```

If `TRF-2024-001` already exists, you get back the original result. No duplicate transfer is created.

## Generating good references

A reference must be unique per business. Collisions cause `409 Conflict` errors. Use a format that is deterministic for the underlying intent — so a retry naturally produces the same value.

Good patterns:

```
TRF-{orderId}                  → TRF-1042
TRF-{orderId}-{attemptNumber}  → TRF-1042-1
{date}-{uuid}                  → 2024-01-15-f47ac10b-58cc
PAYOUT-{payrollId}-{employeeId} → PAYOUT-JAN24-EMP009
```

Avoid using timestamps alone as references — if you generate a new timestamp on retry, you lose idempotency.

## Conflict errors

Submitting a request that conflicts with an existing record in a way that cannot be resolved returns `409 Conflict`:

```json
{
  "success": false,
  "error": {
    "code": "CONFLICT",
    "message": "Customer with this reference already exists"
  },
  "requestId": "req_abc123def456",
  "timestamp": "2024-01-15T10:00:00Z"
}
```

`409` means the conflict is permanent — retrying the same request will not resolve it. You need to either use a different reference or handle the existing record.

## Exponential backoff for server errors

For `500` or `503` responses, retry with exponential backoff:

```javascript
async function withRetry(fn, maxAttempts = 4) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxAttempts || err.status < 500) throw err;
      const delay = Math.min(1000 * 2 ** attempt, 30000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
```

Do not retry `4xx` errors — they indicate a problem with the request itself that will not resolve on its own.

## Summary

| Situation             | Action                                                                    |
| --------------------- | ------------------------------------------------------------------------- |
| Request timed out     | Check by reference first, then retry with the same reference if not found |
| Got `5xx` response    | Retry with exponential backoff using the same reference                   |
| Got `409 Conflict`    | Do not retry — handle the existing record or use a new reference          |
| Got `4xx` (not `409`) | Do not retry — fix the request                                            |