> For the complete index of the 402pay docs, see [llms.txt](https://developer.402pay.co/llms.txt).

# Recurring and repeat payments

Bill subscriptions and invoices with one payment per period, sent to the customer on your schedule.

402pay has no subscription object and never charges anyone on its own: crypto can't be pulled from a customer's wallet, and cards aren't kept on file. Bill each period with a payment of its own, created by your server on your schedule, and send the customer its `url`.

## One payment per period

Make the `reference` name the subscription and the period, such as `sub_42-2026-10`. A retried run of your billing job then gets the same payment back, and each period gets its own.

Bill a period, cURL:

```bash
curl -X POST "https://dash.402pay.co/api/v1/payments" \
  -H "Authorization: Bearer $PAY402_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4900,
    "currency": "USD",
    "reference": "sub_42-2026-10",
    "description": "Pro plan, October 2026",
    "customer_email": "harper.wilson@example.com",
    "success_url": "https://example.com/billing",
    "metadata": {
      "subscription_id": "sub_42",
      "period": "2026-10"
    }
  }'
```

Bill a period, Node.js:

```js
const response = await fetch("https://dash.402pay.co/api/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAY402_SECRET_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 4900,
    currency: "USD",
    reference: "sub_42-2026-10",
    description: "Pro plan, October 2026",
    customer_email: "harper.wilson@example.com",
    success_url: "https://example.com/billing",
    metadata: {
      subscription_id: "sub_42",
      period: "2026-10"
    }
  }),
});
const { data } = await response.json();
```

Bill a period, Python:

```python
import os

import requests

response = requests.post(
    "https://dash.402pay.co/api/v1/payments",
    headers={
        "Authorization": f"Bearer {os.environ['PAY402_SECRET_KEY']}",
    },
    json={
        "amount": 4900,
        "currency": "USD",
        "reference": "sub_42-2026-10",
        "description": "Pro plan, October 2026",
        "customer_email": "harper.wilson@example.com",
        "success_url": "https://example.com/billing",
        "metadata": {
            "subscription_id": "sub_42",
            "period": "2026-10"
        }
    },
)
data = response.json()["data"]
```

- `customer_email` links the payment to the customer, adding one if they're new, and checkout doesn't ask them for it again.
- Put the subscription and period in `metadata` too, so you can list and filter them later.
- The amount is fixed per payment. For a plan change mid-period, bill the new price on the next period's payment, or cancel this one and create it again.

## Send it to the customer

402pay doesn't email customers. Send the payment's `url` yourself, in your billing email or in your app, with the amount and the period it covers. The same link works until the payment is paid, expires or is canceled.

## Due dates and reminders

- Set `expires_at` to the end of your grace period, up to 30 days out. Without it, a payment expires after 24 hours, which is short for an invoice.
- A payment never expires while the customer has a checkout open for it. When it does, you receive `payment.expired`.
- An expired payment frees its reference. To give a late customer another chance, create the payment again with the same reference and send the new `url`.

## Track what's paid

Mark the period paid on `payment.succeeded`, whose payment carries your reference and metadata. To see a subscription's history, filter payments by its metadata.

A subscription's payments, cURL:

```bash
curl -g "https://dash.402pay.co/api/v1/payments?metadata[subscription_id]=sub_42&status=succeeded" \
  -H "Authorization: Bearer $PAY402_SECRET_KEY"
```

A subscription's payments, Node.js:

```js
const response = await fetch("https://dash.402pay.co/api/v1/payments?metadata[subscription_id]=sub_42&status=succeeded", {
  headers: {
    Authorization: `Bearer ${process.env.PAY402_SECRET_KEY}`,
  },
});
const { data } = await response.json();
```

A subscription's payments, Python:

```python
import os

import requests

response = requests.get(
    "https://dash.402pay.co/api/v1/payments?metadata[subscription_id]=sub_42&status=succeeded",
    headers={
        "Authorization": f"Bearer {os.environ['PAY402_SECRET_KEY']}",
    },
)
data = response.json()["data"]
```

`GET /payments?customer_id=…` lists every payment of one customer, across subscriptions.

## Good to know

- A blocked customer can't pay: checkout refuses their email with 403 `customer_blocked`. Block or unblock someone in the dashboard or with [`PATCH /customers/{id}`](https://developer.402pay.co/api/customers/update.md).
- A [payment link](https://developer.402pay.co/guides/payment-links.md) can take the same price again and again, but its payments have no reference, so you match them to customers by email rather than to periods. For billing, a payment per period is easier to reconcile.
- Amounts are in your currency, and checkout quotes the coin amount when the customer pays, so a period costs the same whatever a coin's price does.
