> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.senjaropay.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.senjaropay.com/_mcp/server.

# Card payments

Collect a card payment by opening a hosted checkout, then confirm the result with a webhook. If the webhook does not arrive, read the payment with the status endpoint.

Both calls are **POST** to `https://api.senjaropay.com`. Send **`Content-Type: application/json`**, **`x-api-key`**, and **`x-api-secret`**.

Send **`x-idempotency-key`** only on `POST /senjaropay/merchant/card/pay`. The status call does not use it.

## Authentication

| Header              | Pay | Status | Description                                                  |
| ------------------- | --- | ------ | ------------------------------------------------------------ |
| `Content-Type`      | Yes | Yes    | `application/json`                                           |
| `x-api-key`         | Yes | Yes    | Dashboard **Public Key** ([Authentication](/authentication)) |
| `x-api-secret`      | Yes | Yes    | Secret key. Server-side only.                                |
| `x-idempotency-key` | Yes | No     | A new UUID for each card payment                             |

Generate the idempotency key in your backend when you create a payment. Do not reuse a key across different payments.

```js
import { randomUUID } from "node:crypto";

const idempotencyKey = randomUUID();
```

```python
import uuid

idempotency_key = str(uuid.uuid4())
```

Reuse a key only when you are retrying the **same** card payment request (same amount, customer, and webhook). A repeated key returns the original result and does not open a second checkout.

## Create a card payment

```
POST https://api.senjaropay.com/senjaropay/merchant/card/pay
```

SenjaroPay creates a pending payment and returns `checkout_url`. Redirect the customer to that URL to enter card details. Store `data.reference`. You will need it for webhooks and for status checks.

The session stops accepting payment at `data.expires_at`.

### Request

```json
{
  "payment_type": "card",
  "details": {
    "amount": 1000,
    "currency": "TZS"
  },
  "customer": {
    "firstname": "John",
    "lastname": "Customer",
    "phone_number": "255740000001",
    "email": "test.customer@example.com",
    "country": "Tanzania",
    "city": "Dar es Salaam",
    "address": "Sample Street"
  },
  "webhook_url": "https://example.com/webhooks/senjaropay/mock-callback"
}
```

```bash
curl -sS -X POST "https://api.senjaropay.com/senjaropay/merchant/card/pay" \
  -H "Content-Type: application/json" \
  -H "x-api-key: ${SENJARO_API_KEY}" \
  -H "x-api-secret: ${SENJARO_API_SECRET}" \
  -H "x-idempotency-key: $(uuidgen)" \
  -d '{
    "payment_type": "card",
    "details": { "amount": 1000, "currency": "TZS" },
    "customer": {
      "firstname": "John",
      "lastname": "Customer",
      "phone_number": "255740000001",
      "email": "test.customer@example.com",
      "country": "Tanzania",
      "city": "Dar es Salaam",
      "address": "Sample Street"
    },
    "webhook_url": "https://example.com/webhooks/senjaropay/mock-callback"
  }'
```

### Response

```json
{
  "status": "success",
  "code": 200,
  "data": {
    "amount": {
      "currency": "TZS",
      "value": 1000
    },
    "expires_at": "2026-09-28T09:30:16.066Z",
    "object": "payment",
    "payment_type": "card",
    "reference": "CARD-CK7EVXQVK7CP",
    "status": "pending",
    "checkout_url": "https://cardpay.com/MI/payment.html?uuid=GgffdGF52eg6gA2AFd64Dchf"
  }
}
```

`data.status` is `pending` until the customer finishes checkout. Do not fulfill the order on this response.

### Request fields

| Field                   | Type    | Required | Description                                          |
| ----------------------- | ------- | -------- | ---------------------------------------------------- |
| `payment_type`          | string  | Yes      | Must be `card`                                       |
| `details.amount`        | integer | Yes      | Amount to collect, in major units                    |
| `details.currency`      | string  | Yes      | `TZS`                                                |
| `customer.firstname`    | string  | Yes      | Customer first name                                  |
| `customer.lastname`     | string  | Yes      | Customer last name                                   |
| `customer.phone_number` | string  | Yes      | MSISDN, digits only, no `+` (example `255740000001`) |
| `customer.email`        | string  | Yes      | Customer email                                       |
| `customer.country`      | string  | Yes      | Billing country                                      |
| `customer.city`         | string  | Yes      | Billing city                                         |
| `customer.address`      | string  | Yes      | Billing address                                      |
| `webhook_url`           | string  | Yes      | HTTPS URL that receives the payment result           |

## Check status

Use this when the [webhook](/webhooks) is late, dropped, or fails your signature check. It reads the payment. It does not create a new charge.

```
POST https://api.senjaropay.com/senjaropay/merchant/card/status
```

Pass the `reference` returned by card pay. Authenticate with `x-api-key` and `x-api-secret` only.

### Request

```json
{
  "reference": "CARD-8LBDCZ6Q6XZS"
}
```

```bash
curl -sS -X POST "https://api.senjaropay.com/senjaropay/merchant/card/status" \
  -H "Content-Type: application/json" \
  -H "x-api-key: ${SENJARO_API_KEY}" \
  -H "x-api-secret: ${SENJARO_API_SECRET}" \
  -d '{"reference":"CARD-8LBDCZ6Q6XZS"}'
```

### Response

```json
{
  "status": "success",
  "code": 200,
  "data": {
    "object": "payment",
    "payment_type": "card",
    "reference": "CARD-8LBDCZ6Q6XZS",
    "status": "pending",
    "amount": {
      "currency": "TZS",
      "value": 1000
    },
    "provider_status": null,
    "created_at": "2026-09-28T07:39:17.000Z",
    "updated_at": "2026-09-28T07:39:17.000Z"
  }
}
```

| Field                  | Description                                                               |
| ---------------------- | ------------------------------------------------------------------------- |
| `data.reference`       | Same reference you stored at checkout creation                            |
| `data.status`          | Current SenjaroPay payment state. `pending` means the result is not final |
| `data.amount`          | Currency and value recorded for the payment                               |
| `data.provider_status` | Processor status. `null` until the card processor reports a result        |
| `data.created_at`      | When the payment was created                                              |
| `data.updated_at`      | When the payment record last changed                                      |

Poll with backoff, and stop when `data.status` is no longer `pending` or when `expires_at` from the create response has passed. Make fulfillment idempotent on `reference` so a late webhook and a status check cannot both fulfill the same order.

## Errors

| HTTP status | Typical `error_code` | When                                             |
| ----------- | -------------------- | ------------------------------------------------ |
| **400**     | `validation_error`   | Missing field, invalid amount, or malformed JSON |
| **401**     | `unauthorized`       | Missing or invalid `x-api-key` or `x-api-secret` |

```json
{
  "status": "error",
  "code": 400,
  "error_code": "validation_error",
  "message": "reference is required"
}
```

Examples on this page are mock values. Keep API keys in server-side secrets.