Recurring Payments
Subscriptions & installments · Automated billing · Card on file · Retry logic
Summary
Recurring Payments (Subscriptions) allow merchants to set up automated billing cycles for customers. Merchants can create open-ended subscriptions or fixed installment plans with configurable frequencies ranging from weekly to annual. The card can be entered by the merchant or by the customer via a hosted payment page.
RapidCents automatically charges stored cards on the scheduled billing date, sends success/decline notifications to both parties, and retries failed payments on a configurable schedule.
Subscription Lifecycle
- Create — Merchant creates a subscription with amount, customer, frequency, start date, and optionally a card. If the merchant enters the card, the subscription is active immediately.
- Customer Activation — If the customer enters the card, they receive an email with a link to the hosted page. The customer enters card details and the subscription becomes active.
- Scheduled Billing — The billing engine runs daily, identifies subscriptions due for charging, and adds them to the payment queue.
- Charge — The payment queue processor charges the stored card. On success, the bill count increments and notifications are sent. On decline, a retry is scheduled.
- Pause / Resume — Merchants can pause billing immediately or at period end, optionally preserving unused prepaid time or confirming an external refund. Resume continues the original schedule or restarts billing today.
- Completion / Cancellation — Installment plans complete after the specified number of bills. Subscriptions continue until manually cancelled.
Subscription Statuses
INACTIVE (Awaiting Card)
Customer-entered card flow: subscription is created but waiting for the customer to provide card details.
ACTIVE
Subscription is active and will be charged on the next billing date. Card is on file.
PAUSED
Service and automatic billing are stopped. Resume restores prepaid time or starts a new cycle depending on pause behavior.
CANCELLED
Subscription has been cancelled. No further charges will be made.
Processing Flow
Merchant-Entered Card
Customer-Entered Card
Authentication
Public API v1 is server-to-server. Authenticate with a RapidCents rc_sk_ secret key.
Authorization: Bearer {rc_sk_secret_key}
Content-Type: application/json
Accept: application/json
Base URL pattern: /api/v1/subscriptions. Business is resolved from the secret key and is never accepted from the request body.
Public API v1 — Subscriptions
Server-to-server subscription APIs under /api/v1. Authenticate with a RapidCents rc_sk_ secret key. Raw card PAN/CVC is never accepted; use a stored payment_method or the hosted checkout link returned on create.
Pause and resume on API v1 match the dashboard lifecycle: preview unused prepaid time, pause immediately or at period end, preserve time or confirm an external refund, then resume on the original schedule or restart today.
Subscription responses include top-level pause_at, paused_at, and pause_reason, plus a nullable pause object when an unresolved pause exists:
{
"pause": {
"id": "pause-uuid",
"status": "pending",
"unused_time_behavior": "preserve_time",
"paused_at": 1720000000,
"paid_through_at": 1721200000,
"remaining_paid_seconds": 1209600,
"requires_external_refund_confirmation": false,
"resume_mode": null
}
}
API v1 — Create Subscription
POST /api/v1/subscriptions
Create with a catalog price (or items[]) and either a stored payment_method or a hosted payment link when omitted.
POST /api/v1/subscriptions
Authorization: Bearer {rc_sk_...}
{
"customer": "customer-uuid",
"price": "price-uuid",
"payment_method": "pm-uuid",
"start_date": "2026-03-01"
}
API v1 — List / Get Subscription
GET /api/v1/subscriptions
GET /api/v1/subscriptions/{subscription}
Optional list filters: customer, status (active, paused, canceled, completed, pending_payment_method).
API v1 — Cancel Subscription
DELETE /api/v1/subscriptions/{subscription} — cancel immediately (optional reason).
POST /api/v1/subscriptions/{subscription} — schedule or undo cancel at period end:
{
"cancel_at_period_end": true,
"cancellation_details": { "comment": "Customer request" },
"proration_behavior": "none"
}
API v1 — Pause Preview
POST /api/v1/subscriptions/{subscription}/pause/preview
Returns whether unused prepaid time remains and which pause choices are required before calling pause.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | Yes | immediate or period_end |
{
"mode": "immediate"
}
{
"object": "subscription_pause_preview",
"pause_mode": "immediate",
"requires_unused_time_choice": true,
"remaining_paid_days": 14,
"unused_prepaid_amount": 11.30,
"suggested_refund_amount": 11.30
}
API v1 — Pause Subscription
POST /api/v1/subscriptions/{subscription}/pause
mode defaults to immediate for backward compatibility. When prepaid time remains on an immediate pause, unused_time_behavior is required (it is not silently defaulted).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | No | immediate (default) or period_end |
reason | string | No | Optional pause reason |
unused_time_behavior | string | If prepaid remains | preserve_time or external_refund (immediate only) |
refund_confirmed | boolean | If external_refund | Must be true |
refund_reference | string | If external_refund | Non-empty refund reference |
refund_note | string | No | Optional note |
For period_end, do not send unused_time_behavior or refund fields. Service continues until the period ends; automatic future billing stops after that.
{
"mode": "immediate",
"unused_time_behavior": "preserve_time",
"reason": "Temporary pause"
}
{
"mode": "immediate",
"unused_time_behavior": "external_refund",
"refund_confirmed": true,
"refund_reference": "refund_txn_123",
"reason": "Customer refunded unused time"
}
{
"mode": "period_end",
"reason": "Pause after current period"
}
API v1 — Undo Period-End Pause
POST /api/v1/subscriptions/{subscription}/pause-at-period-end/undo
Cancels a scheduled pause-at-period-end before it takes effect. No request body required.
API v1 — Resume Preview
POST /api/v1/subscriptions/{subscription}/resume/preview
Preview how resume will behave, including charge due today and quote_hash when a restart-today charge applies.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
billing_cycle_anchor | string | Yes | now → restart today (from_today); unchanged → original schedule |
{
"billing_cycle_anchor": "now"
}
{
"object": "subscription_resume_preview",
"resume_path": "from_today",
"charge_due_today": 11.30,
"payment_required_to_reactivate": true,
"quote_hash": "abc123...",
"new_period_start": "2026-08-13",
"new_period_end": "2026-08-27"
}
API v1 — Resume Subscription
POST /api/v1/subscriptions/{subscription}/resume
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
billing_cycle_anchor | string | Yes | now or unchanged |
proration_behavior | string | Yes | Must be none |
quote_hash | string | When preview returns one | Pass the hash from resume preview; required for restart-today charges |
idempotency_key | string | No | Also accepted via Idempotency-Key header |
{
"billing_cycle_anchor": "unchanged",
"proration_behavior": "none"
}
{
"billing_cycle_anchor": "now",
"proration_behavior": "none",
"quote_hash": "abc123...",
"idempotency_key": "resume-attempt-1"
}
A stale quote_hash returns a conflict error — request a new resume preview. If payment is required and fails, the subscription stays paused until a successful resume charge.
Frequency Options
| Sequence Value | Label | Interval |
|---|---|---|
1 | Weekly | Every 1 week |
2 | Bi-Weekly | Every 2 weeks |
3 | Monthly | Every 1 month |
4 | Quarterly | Every 3 months |
5 | Twice a Year | Every 6 months |
6 | Annual | Every 12 months |
Scheduling & Payment Queue
RapidCents uses a two-stage billing engine to process recurring payments:
Stage 1 — Subscription Scheduler
The handle:subscriptions command runs daily and evaluates all active subscriptions. For each subscription:
- Calculates the next billing date from
last_transaction_date(orstart_date) andsequence - If today is the billing date and no queue entry exists, creates a
PaymentQueuerecord with statusPENDING - Skips subscriptions that have reached their installment limit
Stage 2 — Payment Queue Processor
The app:handle-payment-queue command processes all pending queue items:
- Charges the stored card via Visa, Mastercard, Discover, or Amex
- On success: marks the queue item as
FULFILLED, incrementsnumber_of_paid_bills, sends success notification - On decline: increments the retry counter, updates
last_failed_attempt_at, sends decline notification
Retry Logic
When a payment is declined, the system automatically retries based on a configurable strategy.
| Attempt | Delay | Description |
|---|---|---|
| 1st attempt | Day 0 | Initial charge on the billing date |
| 2nd attempt | +3 days | First retry after 3 days |
| 3rd attempt | +3 days | Second retry after 6 days total |
| 4th attempt | +3 days | Final retry after 9 days total |
The retry strategy is encoded as "0 3 3 3" where each number represents the number of days before the next attempt. Both merchant and customer receive email notifications for each failed attempt.
Card on File
Recurring payments use securely stored card tokens. The card can be stored in two ways:
- Merchant-entered card: The card is tokenized at subscription creation time and linked to the subscription.
- Customer-entered card: The customer enters card details via the hosted page, which are tokenized and stored.
- Existing payment method: Pass a stored
payment_methodid on create.
All subsequent charges use the stored card token — the raw card data is never stored or re-transmitted.
All Endpoints
Public API v1 (Secret Key)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/subscriptions | Create subscription |
| GET | /api/v1/subscriptions | List subscriptions |
| GET | /api/v1/subscriptions/{id} | Get subscription |
| POST | /api/v1/subscriptions/{id} | Cancel at period end / undo |
| DELETE | /api/v1/subscriptions/{id} | Cancel immediately |
| POST | /api/v1/subscriptions/{id}/pause/preview | Pause preview |
| POST | /api/v1/subscriptions/{id}/pause | Pause (immediate or period end) |
| POST | /api/v1/subscriptions/{id}/pause-at-period-end/undo | Undo scheduled pause |
| POST | /api/v1/subscriptions/{id}/resume/preview | Resume preview |
| POST | /api/v1/subscriptions/{id}/resume | Resume subscription |
| GET | /api/v1/subscriptions/{id}/payments | List subscription payments |
Notifications
| Event | Recipient | Subject | Content |
|---|---|---|---|
| Subscription created (customer card) | Customer | Your Recurring payment is Ready | Amount, frequency, “Pay Now” link |
| Subscription created (merchant card) | Customer | Your Upcoming Recurring Payment & Invoice | Amount, frequency, PDF invoice attachment |
| Payment success | Customer | Your Subscription Payment Was Successful | Amount, card, installment info, transaction date |
| Payment success | Merchant | Subscription Payment Received from {customer} | Amount, card, installment info, transaction date |
| Payment declined | Customer | Your Subscription Payment Was Declined | Decline reason, attempt count |
| Payment declined | Merchant | Subscription Payment Failed for {customer} | Decline reason, attempt count |
Error Handling
| HTTP Code | Scenario | Description |
|---|---|---|
422 | Validation error | Missing or invalid required fields (e.g. unused_time_behavior, quote_hash) |
409 | Conflict | Lifecycle conflict or stale resume quote_hash |
402 | Payment required / declined | Resume charge failed; subscription stays paused |
404 | Not found | Subscription does not exist |
403 | Contract unsigned | Linked contract must be signed before activation |
500 | Server error | Unexpected processing error |