# POST /payments/charge
## Overview
The `/payments/charge` endpoint allows you to create a new payment charge against a customer's stored payment method. This endpoint processes the payment and returns a charge object representing the transaction.
## Authentication
All API requests must include your API key in the `Authorization` header:
```
Authorization: Bearer sk_live_your_api_key_here
```
## Rate Limits
- **Live mode:** 100 requests per second
- **Test mode:** 50 requests per second
Exceeding these limits will result in a `429 Too Many Requests` response.
## Request
### Headers
| Header | Required | Description |
|--------|----------|-------------|
| `Authorization` | Yes | Bearer token with your API key |
| `Content-Type` | Yes | Must be `application/json` |
| `Idempotency-Key` | Recommended | Unique key to prevent duplicate charges |
### Body Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `amount` | integer | Yes | The charge amount in the smallest currency unit (e.g., cents for USD). Must be a positive integer. |
| `currency` | string | Yes | Three-letter ISO 4217 currency code (e.g., `usd`, `eur`). |
| `customer_id` | string (UUID) | Yes | The unique identifier of the customer being charged. |
| `payment_method_id` | string (UUID) | Yes | The unique identifier of the payment method to charge. |
| `idempotency_key` | string | Yes | A unique string to ensure idempotent processing of the request. |
| `metadata` | object | No | A set of key-value pairs for storing additional information about the charge. |
### Example Request
```bash
curl -X POST https://api.example.com/v1/payments/charge \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"amount": 2500,
"currency": "usd",
"customer_id": "cus_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"payment_method_id": "pm_12345678-abcd-efgh-ijkl-9876543210ab",
"idempotency_key": "charge_abc123_20240115",
"metadata": {
"order_id": "ORD-2024-001",
"description": "Pro Plan - Monthly Subscription"
}
}'
```
## Response
### Success Response (200 OK)
```json
{
"id": "ch_1234567890abcdef",
"object": "charge",
"status": "succeeded",
"amount": 2500,
"currency": "usd",
"customer_id": "cus_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"payment_method_id": "pm_12345678-abcd-efgh-ijkl-9876543210ab",
"created_at": "2024-01-15T10:30:00Z",
"failure_reason": null,
"metadata": {
"order_id": "ORD-2024-001",
"description": "Pro Plan - Monthly Subscription"
}
}
```
### Error Responses
| Status Code | Error Code | Description |
|-------------|------------|-------------|
| `400` | `invalid_amount` | The amount must be a positive integer. |
| `400` | `invalid_currency` | The currency code is not a valid ISO 4217 code. |
| `401` | `unauthorized` | Invalid or missing API key. |
| `402` | `card_declined` | The payment method was declined. |
| `404` | `customer_not_found` | No customer found with the provided ID. |
| `404` | `payment_method_not_found` | No payment method found with the provided ID. |
| `409` | `duplicate_idempotency_key` | A request with this idempotency key has already been processed. |
| `429` | `rate_limit_exceeded` | Too many requests. Please retry after the time indicated in the `Retry-After` header. |
## Webhooks
### charge.succeeded
Sent when a charge is successfully processed.
```json
{
"event": "charge.succeeded",
"data": {
"id": "ch_1234567890abcdef",
"status": "succeeded",
"amount": 2500,
"currency": "usd",
"customer_id": "cus_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2024-01-15T10:30:00Z"
}
}
```
### charge.failed
Sent when a charge attempt fails.
```json
{
"event": "charge.failed",
"data": {
"id": "ch_1234567890abcdef",
"status": "failed",
"amount": 2500,
"currency": "usd",
"customer_id": "cus_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2024-01-15T10:30:00Z",
"failure_reason": "insufficient_funds"
}
}
```
## Notes
- Charges are processed asynchronously. A `pending` status indicates the charge is still being processed.
- Use the `idempotency_key` to safely retry failed requests without creating duplicate charges.
- Metadata keys and values must be strings. Maximum 20 key-value pairs per charge.
- All amounts are in the smallest currency unit to avoid floating-point issues.
# POST /payments/charge
Create a charge against a customer's stored payment method.
**Base URL:** `https://api.example.com/v1`
---
## Authentication
Include your API key as a Bearer token. Use `sk_test_` keys in test mode and `sk_live_` keys in production — test keys never hit real payment networks.
```
Authorization: Bearer sk_live_4eC39HqLyjWDarjtT1zdp7dc
```
## Rate limits
| Environment | Limit | Burst |
|---|---|---|
| Live | 100 req/s | 150 req/s for 10s |
| Test | 25 req/s | 50 req/s for 10s |
When rate-limited, the response includes a `Retry-After` header (seconds). Back off accordingly — do not retry in a tight loop.
---
## Request
```http
POST /v1/payments/charge
Content-Type: application/json
Authorization: Bearer sk_live_4eC39HqLyjWDarjtT1zdp7dc
```
### Parameters
| Field | Type | Required | Description |
|---|---|---|---|
| `amount` | `integer` | ✓ | Amount in smallest currency unit (e.g., `2500` = $25.00 USD). Must be ≥ 50. |
| `currency` | `string` | ✓ | Lowercase [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) code. We support 135 currencies — see [supported currencies](/docs/currencies). |
| `customer_id` | `string<uuid>` | ✓ | The customer to charge. Must have at least one verified payment method. |
| `payment_method_id` | `string<uuid>` | ✓ | Must belong to the specified customer. |
| `idempotency_key` | `string` | ✓ | Unique per-request key (max 255 chars). We store keys for 24 hours — retries within that window return the original response without reprocessing. |
| `metadata` | `object` | — | Up to 20 key-value pairs. Keys max 40 chars, values max 500 chars. Not used in processing — purely for your records. |
> **⚠️ Amount is in cents, not dollars.** Passing `25` charges $0.25, not $25.00. This is the most common integration mistake.
### Example
```bash
curl -X POST https://api.example.com/v1/payments/charge \
-H "Authorization: Bearer sk_live_4eC39HqLyjWDarjtT1zdp7dc" \
-H "Content-Type: application/json" \
-d '{
"amount": 2500,
"currency": "usd",
"customer_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"payment_method_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"idempotency_key": "ord_8v3kQ9_charge_1",
"metadata": {
"order_id": "ord_8v3kQ9",
"line_items": "3"
}
}'
```
---
## Response
### Charge object
| Field | Type | Description |
|---|---|---|
| `id` | `string` | Unique charge identifier. Starts with `ch_`. |
| `status` | `string` | One of: `pending`, `succeeded`, `failed`. |
| `amount` | `integer` | Confirmed amount in smallest currency unit. |
| `currency` | `string` | ISO 4217 currency code. |
| `created_at` | `string<datetime>` | ISO 8601 timestamp. |
| `failure_reason` | `string\|null` | `null` unless `status` is `failed`. See [failure codes](#failure-codes). |
| `metadata` | `object` | The metadata you sent. |
### 200 — Charge created
```json
{
"id": "ch_2kF8xLmN4qRs",
"status": "pending",
"amount": 2500,
"currency": "usd",
"created_at": "2026-06-09T14:22:08Z",
"failure_reason": null,
"metadata": {
"order_id": "ord_8v3kQ9",
"line_items": "3"
}
}
```
Note: A `200` with `status: "pending"` means the charge was accepted, not completed. Listen for webhooks to confirm the outcome.
### Error codes
| HTTP | Code | What went wrong |
|---|---|---|
| 400 | `invalid_amount` | Amount is missing, negative, or below the 50-cent minimum. |
| 400 | `invalid_currency` | Not a recognized ISO 4217 code. |
| 400 | `payment_method_mismatch` | The payment method doesn't belong to this customer. |
| 401 | `authentication_failed` | API key is missing, expired, or invalid. |
| 402 | `card_declined` | Issuer declined. Check `failure_reason` for specifics: `insufficient_funds`, `lost_card`, `expired_card`, etc. |
| 404 | `customer_not_found` | No customer with this ID. |
| 404 | `payment_method_not_found` | No payment method with this ID. |
| 409 | `idempotency_conflict` | Same key was used with different parameters. |
| 422 | `currency_not_supported` | We don't support this currency for the customer's payment method type. |
| 429 | `rate_limited` | Back off per `Retry-After` header. |
| 500 | `processing_error` | Something broke on our end. Safe to retry with the same idempotency key. |
---
## Webhooks
We send webhook events when a charge reaches a terminal state. Both events use the same payload shape.
### `charge.succeeded`
```json
{
"id": "evt_9xK3mPq",
"type": "charge.succeeded",
"created_at": "2026-06-09T14:22:11Z",
"data": {
"id": "ch_2kF8xLmN4qRs",
"status": "succeeded",
"amount": 2500,
"currency": "usd",
"customer_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"payment_method_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"created_at": "2026-06-09T14:22:08Z",
"failure_reason": null
}
}
```
### `charge.failed`
```json
{
"id": "evt_4nB7rWx",
"type": "charge.failed",
"created_at": "2026-06-09T14:22:14Z",
"data": {
"id": "ch_2kF8xLmN4qRs",
"status": "failed",
"amount": 2500,
"currency": "usd",
"customer_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"payment_method_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"created_at": "2026-06-09T14:22:08Z",
"failure_reason": "insufficient_funds"
}
}
```
### Webhook best practices
1. **Verify signatures.** Every webhook includes an `X-Signature-256` header. Validate it before processing — see [webhook security](/docs/webhooks/security).
2. **Respond with 2xx quickly.** We time out after 10 seconds and retry up to 5 times with exponential backoff.
3. **Handle duplicates.** Use `evt_` IDs to deduplicate. We guarantee at-least-once delivery, not exactly-once.
4. **Don't trust webhooks alone for critical flows.** Always reconcile with a GET /payments/charges/{id} call if money movement depends on it.