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

# Environments

> Sandbox and production environments are controlled by your API key

## How environments work

Fingo Pay uses a **single base URL** for both sandbox and production. The environment is determined entirely by the API key you send.

<Info>
  **Base URL** (all environments)

  ```bash theme={"dark"}
  https://api.fingopay.io
  ```
</Info>

|                     | Sandbox                    | Production                    |
| ------------------- | -------------------------- | ----------------------------- |
| **API key prefix**  | `sk_test_***`              | `sk_live_***`                 |
| **Real money**      | No                         | Yes                           |
| **M-Pesa STK Push** | Simulated                  | Live                          |
| **Webhooks**        | Delivered (sandbox secret) | Delivered (production secret) |
| **Idempotency-Key** | Optional                   | Required for POST             |
| **Rate limits**     | Same (600 req/min)         | Same (600 req/min)            |

<Warning>
  Never mix sandbox and production keys. Maintain separate credentials and webhook secrets per environment.
</Warning>

## What is the sandbox?

The sandbox is a full simulation of the Fingo Pay API. It exposes the same endpoints, accepts the same request shapes, returns the same response structures, and delivers webhooks in the same format as production -- but no real money moves and no real M-Pesa STK prompts are sent to phones.

Use sandbox to build, test, and validate your integration before going live.

* **Instant processing.** Sandbox transactions are processed immediately. There is no waiting for M-Pesa callbacks -- outcomes are simulated and webhooks are fired within seconds.
* **Isolated data.** Sandbox transactions, accounts, and balances are completely separate from production. Nothing you do in sandbox affects live data.
* **Pre-loaded balances.** Sandbox accounts come with test balances so you can test charges, payouts, and balance queries without provisioning funds.

<Info>
  The sandbox behaves identically to production in terms of validation, error responses, and webhook signatures. If your integration works correctly in sandbox, it will work in production with minimal changes -- just swap your API key and webhook secret.
</Info>

## Switching environments

<CodeGroup>
  ```bash Sandbox theme={"dark"}
  export FINGO_API_KEY=sk_test_***
  curl -H "Authorization: Bearer $FINGO_API_KEY" \
    "https://api.fingopay.io/v1/balance?accountNo=ACC-TEST-123456"
  ```

  ```bash Production theme={"dark"}
  export FINGO_API_KEY=sk_live_***
  curl -H "Authorization: Bearer $FINGO_API_KEY" \
    "https://api.fingopay.io/v1/balance?accountNo=ACC-LIVE-123456"
  ```
</CodeGroup>

## Sandbox behaviors

Each API area behaves slightly differently in sandbox. The table below summarizes what is simulated and what remains the same.

| API area                  | Sandbox behavior                                                                                                                    |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **M-Pesa C2B (STK Push)** | Simulated. No STK prompt is sent to the phone. The transaction completes automatically based on `forceStatus` (default: `success`). |
| **M-Pesa B2C**            | Simulated. Funds are not disbursed to the recipient. A webhook is fired with the simulated outcome.                                 |
| **M-Pesa B2B**            | Simulated. No real transfer occurs between tills or paybills. A webhook is fired with the simulated outcome.                        |
| **Bank transfers**        | Simulated. No real bank transfer is initiated.                                                                                      |
| **Balances**              | Sandbox accounts have pre-loaded test balances. Transactions debit and credit these balances as they would in production.           |
| **Webhooks**              | Delivered to your configured endpoint using your **sandbox webhook secret**. The signature format is identical to production.       |
| **Idempotency-Key**       | Optional in sandbox (required for POST requests in production).                                                                     |
| **Validation and errors** | Identical to production. Invalid requests return the same error types and codes.                                                    |

## Testing with `forceStatus`

In sandbox, you can force specific transaction outcomes using the `metadata.forceStatus` field. This lets you test success, failure, and timeout flows without waiting for real M-Pesa callbacks.

| `forceStatus` value | Behavior                                          |
| ------------------- | ------------------------------------------------- |
| `success`           | Transaction completes successfully                |
| `failed`            | Transaction fails with a simulated error          |
| `timeout`           | Simulates customer no-response / STK Push timeout |

<Note>
  If you do not include `forceStatus` in your sandbox request, the default outcome is `success`.
</Note>

### Example: Successful charge

```json theme={"dark"}
{
  "phoneNumber": "+254711000000",
  "amount": 150000,
  "merchantTransactionId": "test_success_001",
  "metadata": {
    "forceStatus": "success"
  }
}
```

Your webhook endpoint receives a `transaction.succeeded` event with a simulated M-Pesa processor reference.

### Example: Failed charge

```json theme={"dark"}
{
  "phoneNumber": "+254711000000",
  "amount": 150000,
  "merchantTransactionId": "test_failed_002",
  "metadata": {
    "forceStatus": "failed"
  }
}
```

Your webhook endpoint receives a `transaction.failed` event with a simulated error message.

### Example: Timeout

```json theme={"dark"}
{
  "phoneNumber": "+254711000000",
  "amount": 150000,
  "merchantTransactionId": "test_timeout_003",
  "metadata": {
    "forceStatus": "timeout"
  }
}
```

Your webhook endpoint receives a `transaction.failed` event with a timeout message, simulating a customer who did not respond to the STK Push prompt.

<Warning>
  The `forceStatus` field is ignored in production. Including it in live requests has no effect on the transaction outcome.
</Warning>

<Tip>
  Use test phone numbers like `+254711000000` in sandbox. These are non-billable and will not trigger real STK prompts.
</Tip>

## Pre-go-live testing checklist

Verify each of the following in sandbox before switching to production.

<Steps>
  <Step title="Successful charge flow">
    Create a C2B charge with `forceStatus: "success"` and confirm your webhook endpoint receives a `transaction.succeeded` event. Verify that your system correctly updates the order or payment status.
  </Step>

  <Step title="Failed charge flow">
    Create a C2B charge with `forceStatus: "failed"` and then `forceStatus: "timeout"`. Confirm your webhook endpoint handles both `transaction.failed` scenarios and that your system surfaces the failure to the user or triggers a retry flow.
  </Step>

  <Step title="Payout flows">
    Test B2C and B2B payouts. Verify that webhooks are received and that your system reconciles by `merchantTransactionId` and `transactionId`.
  </Step>

  <Step title="Webhook signature verification">
    Confirm that your endpoint validates the `X-Fingo-Signature` header using your sandbox webhook secret. Reject requests with invalid or expired signatures. See [Webhooks](/webhooks) for implementation details.
  </Step>

  <Step title="Idempotency handling">
    Send the same POST request twice with the same `Idempotency-Key` and verify you receive the original response. Then send a different body with the same key and confirm you receive a `409` conflict error. See [Authentication and Idempotency](/auth) for details.
  </Step>

  <Step title="Error handling">
    Trigger common error scenarios -- missing required fields (`400`), looking up a non-existent transaction (`404`), and reusing a `merchantTransactionId` (`409`). Verify your integration handles each error type correctly. See [Errors](/errors) for the full reference.
  </Step>

  <Step title="Balance verification">
    Check account balances before and after transactions using `GET /v1/balance` to confirm that sandbox balances update as expected.
  </Step>
</Steps>

## Sandbox limitations

<Info>
  The sandbox is designed for integration testing and validation. It does not replicate every aspect of the production environment.

  * **No phone interaction.** M-Pesa STK Push prompts are not sent to real phones. Transaction outcomes are determined by `forceStatus`.
  * **No real money movement.** Balances are simulated. No funds are transferred to or from any M-Pesa wallet or bank account.
  * **Instant processing.** Sandbox transactions complete immediately. Production transactions depend on M-Pesa and bank processing times, which can range from a few seconds to several minutes.
  * **Scale behavior.** While sandbox enforces the same rate limits (600 req/min), it does not replicate production-level latency or throughput characteristics under high load.
</Info>
