Save and reuse payment methods

Use this flow when you need to save a customer’s card and charge it later without asking them to enter card details again. It works well for variable monthly billing, usage-based charges, add-ons to a subscription, or renewals controlled by your system.

The customer always enters their card on a Recurrente-hosted checkout. Your system stores only the payment_method_id returned by Recurrente and uses it to create future charges.

Before saving a card, make sure the customer accepts clear terms for future charges: frequency, how the amount is calculated, when charges happen, and how they can cancel or update their payment method.

Flow overview

  1. Create or identify the customer in Recurrente.
  2. Create a checkout with mode: "setup" to tokenize the card without charging it.
  3. Send the checkout_url to the customer.
  4. When the customer completes the checkout, store the payment_method.id.
  5. Use POST /api/one_time_payments to charge that saved method when you know the final amount.

Step 1: Create or identify the customer

If you already have a Recurrente customer, pass their customer_id when creating the tokenization checkout. If you only have the user_id, you can pass that instead.

$curl -X POST https://app.recurrente.com/api/customers \
> -H "X-SECRET-KEY: your_secret_key" \
> -H "Content-Type: application/json" \
> -d '{
> "email": "customer@example.com",
> "full_name": "Ana Lopez"
> }'

The response includes the customer id:

1{
2 "id": "cus_abc123",
3 "user_id": "us_123456",
4 "email": "customer@example.com",
5 "name": "Ana Lopez"
6}

Step 2: Create a tokenization checkout

Create a checkout with mode: "setup". This checkout does not charge anything; it only saves the card for future use.

$curl -X POST https://app.recurrente.com/api/checkouts \
> -H "X-SECRET-KEY: your_secret_key" \
> -H "Content-Type: application/json" \
> -d '{
> "mode": "setup",
> "customer_id": "cus_abc123",
> "success_url": "https://yoursite.com/payment-method-saved",
> "cancel_url": "https://yoursite.com/payment-method-cancelled",
> "metadata": {
> "billing_agreement_id": "ba_789"
> }
> }'

Recurrente returns a hosted URL:

1{
2 "id": "ch_eegw9j5zgqoae3ms",
3 "checkout_url": "https://app.recurrente.com/checkout-session/ch_eegw9j5zgqoae3ms"
4}

Redirect the customer to checkout_url, or send it by email, WhatsApp, or inside your app.

Step 3: Store the payment_method_id

When the customer completes the checkout, Recurrente emits setup_intent.succeeded. The saved method is inside data.checkout.payment_method.

1{
2 "eventType": "setup_intent.succeeded",
3 "eventId": "act_123",
4 "data": {
5 "event_type": "setup_intent.succeeded",
6 "status": "succeeded",
7 "checkout": {
8 "id": "ch_eegw9j5zgqoae3ms",
9 "status": "paid",
10 "payment_method": {
11 "id": "pay_m_7v5ie3pw",
12 "type": "card",
13 "card": {
14 "last4": "4242",
15 "network": "visa"
16 }
17 }
18 },
19 "customer": {
20 "id": "cus_abc123",
21 "email": "customer@example.com"
22 }
23 }
24}

Store data.checkout.payment_method.id. This is the token you will use for future charges.

If you do not receive webhooks, retrieve the checkout after the customer returns to your success_url: GET /api/checkouts/{id}. Once the checkout is complete, the response includes payment_method.id.

$curl https://app.recurrente.com/api/checkouts/ch_eegw9j5zgqoae3ms \
> -H "X-SECRET-KEY: your_secret_key"
1{
2 "id": "ch_eegw9j5zgqoae3ms",
3 "status": "paid",
4 "payment_method": {
5 "id": "pay_m_7v5ie3pw",
6 "type": "card",
7 "card": {
8 "last4": "4242",
9 "network": "visa"
10 }
11 }
12}

Step 4: Charge later with a variable amount

When your system knows the amount to charge, create a one-time payment with the saved payment_method_id. Use a unique idempotency key per charge attempt to avoid duplicates if you need to retry the request.

$curl -X POST https://app.recurrente.com/api/one_time_payments \
> -H "X-SECRET-KEY: your_secret_key" \
> -H "Idempotency-Key: invoice-2026-06-cus-abc123" \
> -H "Content-Type: application/json" \
> -d '{
> "payment_method_id": "pay_m_7v5ie3pw",
> "items": [
> {
> "name": "June usage",
> "amount_in_cents": 87500,
> "currency": "GTQ",
> "quantity": 1
> }
> ]
> }'

Successful response:

1{
2 "object": "one_time_payment",
3 "id": "on_123456789",
4 "status": "paid"
5}

If the bank declines the charge, Recurrente returns an error and you should not reuse the same Idempotency-Key for a new charge attempt. Create a new attempt with a different key when the customer confirms they want to retry or updates their payment method.

Variable charges tied to a subscription

If the customer already has a subscription and you want to add variable charges, you have two options.

To immediately charge the subscription’s saved payment method:

$curl -X POST https://app.recurrente.com/api/one_time_payments \
> -H "X-SECRET-KEY: your_secret_key" \
> -H "Idempotency-Key: su-nehndm7j-extra-june" \
> -H "Content-Type: application/json" \
> -d '{
> "subscription_id": "su_nehndm7j",
> "amount_in_cents": 5000,
> "description": "Additional usage charge",
> "mode": "now"
> }'

To add the charge to the next subscription cycle:

$curl -X POST https://app.recurrente.com/api/one_time_payments \
> -H "X-SECRET-KEY: your_secret_key" \
> -H "Content-Type: application/json" \
> -d '{
> "subscription_id": "su_nehndm7j",
> "amount_in_cents": 5000,
> "description": "Additional usage charge",
> "mode": "next_cycle"
> }'

mode: "next_cycle" creates a pending invoice_item. It also supports negative amounts for credits.

Update the saved card

When the customer needs to change cards, create another mode: "setup" checkout. After completion, store the new payment_method.id.

If the method belongs to a subscription, update it with:

$curl -X PUT https://app.recurrente.com/api/subscriptions/su_nehndm7j \
> -H "X-SECRET-KEY: your_secret_key" \
> -H "Content-Type: application/json" \
> -d '{
> "payment_method_id": "pay_m_new123"
> }'

If you only use variable charges with POST /api/one_time_payments, store the new payment_method_id in your system and use it for future charges.

Considerations

  • POST /api/one_time_payments requires one-time payments to be enabled for your account.
  • The payment_method_id must belong to a customer in your account.
  • Future charges can fail even if tokenization succeeded. Handle bank errors and let the customer update their card.
  • Real webhooks are only sent for production events. To validate the complete tokenization flow, use a LIVE key; the setup checkout does not charge an amount.
  • Never collect or store raw card numbers directly. Always use the Recurrente-hosted checkout.