Skip to navigation

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:

{
"id": "cus_abc123",
"user_id": "us_123456",
"email": "customer@example.com",
"name": "Ana Lopez"
}

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.

When saving a new card, Recurrente verifies the account with a zero-amount authorization, without capturing funds. An approved authorization with a permanent token generates setup_intent.succeeded; a decline or failed verification generates setup_intent.cancelled, and the card is not enabled for charges. Saving a card does not guarantee that future charges will succeed or replace authentication that a later payment may require.

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:

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

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.

{
"eventType": "setup_intent.succeeded",
"eventId": "act_123",
"data": {
"event_type": "setup_intent.succeeded",
"status": "succeeded",
"checkout": {
"id": "ch_eegw9j5zgqoae3ms",
"status": "paid",
"payment_method": {
"id": "pay_m_7v5ie3pw",
"type": "card",
"card": {
"last4": "4242",
"network": "visa"
}
}
},
"customer": {
"id": "cus_abc123",
"email": "customer@example.com"
}
}
}

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"
{
"id": "ch_eegw9j5zgqoae3ms",
"status": "paid",
"payment_method": {
"id": "pay_m_7v5ie3pw",
"type": "card",
"card": {
"last4": "4242",
"network": "visa"
}
}
}

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:

{
"object": "one_time_payment",
"id": "on_123456789",
"status": "paid"
}

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.