Recurring Payment API
Create and manage recurring subscriptions tied to a saved card token. The first charge can be executed immediately on creation, or deferred for free trials and future-dated billing. 3D Secure verification may be initiated on the subscription creation request to authenticate the cardholder — subsequent scheduled charges are merchant-initiated and typically exempt from 3DS.
Overview
Use this endpoint to create, update, retrieve, and cancel subscription plans. Each plan is tied to a cardToken from the Tokenization API and reuses your existing billing, order, and device contexts.
- Create a new subscription
- Retrieve details of an existing subscription
- Update plan parameters or pause/cancel
- Cancel to immediately deactivate
1. Create Subscription
Endpoint
Live
POST https://live.facilero.com/api/v1/payments/card/recurringSandbox
POST https://sandbox.facilero.com/api/v1/payments/card/recurringRequest Headers
Content-Type: application/jsonAuthorization: Bearer <AUTH_TOKEN>
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
requestId | String | Yes | Unique identifier for this subscription creation. |
mid | String | Yes | Merchant account ID. |
cardToken | String | Yes | Vaulted token obtained from the Tokenization API. |
plan | Object (Plan) | Yes | Subscription schedule (amount, currency, frequency, interval, dates). See Plan Object below. |
billingDetails | Object (BillingDetails) | Yes | Customer billing/KYC info. |
order | Object (Order) | Yes | Order context to associate with charges. |
device | Object (Device) | Yes | Device/browser info for risk scoring. |
skipFirstCharge | Boolean | No | Default false. When true, no initial charge is executed — subscription starts as TRIALING. First charge occurs on startDate. Use for free trials, future-dated billing, or card-on-file setup. |
callbackUrl | String | Yes | URL to receive subscription event webhooks (success/failure of each charge attempt). |
successRedirectUrl | String | Yes | URL to redirect the customer after successful 3DS verification. |
failureRedirectUrl | String | Yes | URL to redirect the customer after failed or cancelled 3DS verification. |
metadata | Map<String, String> | No | Free-form key/value pairs for your own reference. |
Important — 3D Secure on subscription creation3DS verification may be initiated when the subscription is created, regardless of the
skipFirstChargeflag.When 3DS is triggered, the response returns:
transactionStatus: "CUSTOMER_VERIFICATION"challengeUrlRedirect the customer to
challengeUrlto complete the challenge.After completion, they are sent to:
successRedirectUrlfailureRedirectUrlBoth redirect URLs are required on every subscription request.
Plan Object
| Field | Type | Required | Description |
|---|---|---|---|
amount | String | Yes | Decimal amount per billing cycle (precision per-currency). |
currency | String | Yes | ISO 4217 code (e.g. EUR, USD). |
frequency | String | Yes | Billing cadence. One of: DAILY, WEEKLY, MONTHLY, CUSTOM. CUSTOM charges every interval days — use for non-standard periods (e.g. every 45 days). |
interval | Integer | Yes | Multiplier for frequency. Examples: WEEKLY + 2 = every 2 weeks, MONTHLY + 3 = quarterly, CUSTOM + 45 = every 45 days. |
startDate | String | Yes | ISO 8601 date when billing begins (e.g. 2025-08-01). |
endDate | String | No | ISO 8601 date when billing ends. Omit for open-ended subscriptions. |
Example Request Body (immediate charge)
{
"requestId": "req-sub-001",
"mid": "merchant456",
"cardToken": "tok_abc123xyz",
"plan": {
"amount": "19.99",
"currency": "EUR",
"frequency": "MONTHLY",
"interval": 1,
"startDate": "2025-08-01",
"endDate": "2026-07-31"
},
"billingDetails": {
"firstName": "Jane",
"lastName": "Doe",
"address1": "123 Main St",
"city": "Dublin",
"state": "Leinster",
"country": "IE",
"postalCode": "D01X0F5",
"phone": "+353851234567",
"email": "[email protected]",
"dateOfBirth": "1990-05-20"
},
"callbackUrl": "https://yourserver.com/subscription-webhook",
"successRedirectUrl": "https://yoursite.com/subscription/success",
"failureRedirectUrl": "https://yoursite.com/subscription/failure",
"metadata": {
"subscriptionName": "PremiumPlan"
}
}Example Request Body (free trial / deferred start)
{
"requestId": "req-sub-002",
"mid": "merchant456",
"cardToken": "tok_abc123xyz",
"skipFirstCharge": true,
"plan": {
"amount": "19.99",
"currency": "EUR",
"frequency": "MONTHLY",
"interval": 1,
"startDate": "2025-08-01",
"endDate": "2026-07-31"
},
"callbackUrl": "https://yourserver.com/subscription-webhook",
"successRedirectUrl": "https://yoursite.com/subscription/success",
"failureRedirectUrl": "https://yoursite.com/subscription/failure",
"metadata": {
"subscriptionName": "FreeTrial"
}
}Response Body
POST /recurring creates the subscription and (by default) performs the first charge immediately. When skipFirstCharge=true, no charge is executed and the subscription starts as TRIALING.
| Field | Type | Description |
|---|---|---|
subscriptionId | String? | Unique identifier for the subscription. null when the first charge was declined (FAILED/BLOCKED) — no subscription is created, and you may retry the request. |
transactionId | String? | Unique identifier for the first charge transaction. null when skipFirstCharge=true. |
transactionStatus | String? | Status of the first charge: SUCCEED, FAILED, CUSTOMER_VERIFICATION, etc. null when skipFirstCharge=true. |
declineCode | Int? | Decline code if the first charge failed (nullable). |
challengeUrl | String? | 3DS challenge URL the customer must be redirected to when transactionStatus is CUSTOMER_VERIFICATION. null otherwise. |
challengeUrlIframe | String? | Same as challengeUrl but optimized for iframe embedding. null when no 3DS challenge is required. |
nextChargeDate | String? | ISO 8601 date of the next scheduled charge. null when the first charge was declined. When skipFirstCharge=true, equals plan.startDate (first charge). Otherwise startDate + frequency * interval (second charge). |
Example Response (immediate charge)
{
"subscriptionId": "sub_839201748291034",
"transactionId": "839201748291035",
"transactionStatus": "SUCCEED",
"declineCode": null,
"nextChargeDate": "2025-09-01"
}Example Response (first charge declined)
When the first charge fails, no subscription is created. You can safely retry the same request.
{
"subscriptionId": null,
"transactionId": "839201748291036",
"transactionStatus": "FAILED",
"declineCode": 203,
"nextChargeDate": null
}Example Response (first charge requires 3DS)
When the first charge triggers 3D Secure, redirect the customer to challengeUrl. After the customer completes the challenge, they are redirected to your successRedirectUrl or failureRedirectUrl.
{
"subscriptionId": "sub_839201748291037",
"transactionId": "839201748291038",
"transactionStatus": "CUSTOMER_VERIFICATION",
"declineCode": null,
"challengeUrl": "https://sandbox.api.facilero.com/api/v1/3ds/mock/839201748291038",
"challengeUrlIframe":"https://sandbox.api.facilero.com/api/v1/3ds/mock/839201748291038?iframe=true",
"nextChargeDate": "2025-09-01"
}Example Response (skipFirstCharge=true)
{
"subscriptionId": "sub_839201748291035",
"transactionId": null,
"transactionStatus": null,
"declineCode": null,
"nextChargeDate": "2025-08-01"
}2. Retrieve Subscription
Endpoint
Live
GET https://live.facilero.com/api/v1/payments/card/recurring/{subscriptionId}Sandbox
GET https://sandbox.facilero.com/api/v1/payments/card/recurring/{subscriptionId}Request Headers
Authorization: Bearer <AUTH_TOKEN>
Response Body
Same fields as Create Subscription response, plus echo of the original plan, billingDetails, and metadata.
Example Response Body
{
"subscriptionId": "sub_3344",
"status": "ACTIVE",
"nextChargeDate": "2025-09-01",
"createdAt": "2025-07-14T10:20:00Z",
"plan": {
"amount": "19.99",
"currency": "EUR",
"frequency": "MONTHLY",
"interval": 1,
"startDate": "2025-08-01",
"endDate": "2026-07-31"
},
"billingDetails": {
"firstName": "Jane",
"lastName": "Doe",
"address1": "123 Main St",
"city": "Dublin",
"state": "Leinster",
"country": "IE",
"postalCode": "D01X0F5",
"phone": "+353851234567",
"email": "[email protected]",
"dateOfBirth": "1990-05-20"
},
"metadata": {
"subscriptionName": "PremiumPlan",
"customerId": "cust-789"
}
}3. Update or Pause Subscription
Endpoint
Live
PUT https://live.facilero.com/api/v1/payments/card/recurring/{subscriptionId}Sandbox
PUT https://sandbox.facilero.com/api/v1/payments/card/recurring/{subscriptionId}Request Headers
Content-Type: application/jsonAuthorization: Bearer <AUTH_TOKEN>
Request Body
Include any of the mutable fields from the Plan Object or set:
Request Body DTO
| Field | Type | Required | Description |
|---|---|---|---|
plan.amount | String | No | Update the subscription’s amount per billing cycle (precision per-currency). |
plan.currency | String | No | Update the ISO 4217 currency code (e.g. EUR, USD). |
plan.frequency | String | No | Update the billing cadence (DAILY, WEEKLY, MONTHLY, CUSTOM). |
plan.interval | Integer | No | Update the multiplier for frequency (e.g. 2 + WEEKLY = every 2 weeks). |
plan.startDate | String | No | Change the ISO 8601 start date (e.g. 2025-08-01). |
plan.endDate | String | No | Change or add the ISO 8601 end date; omit to leave open-ended. |
status | String | No | Transition the subscription state: PENDING, TRIALING, ACTIVE, PAUSED, or CANCELED. |
Example JSON
PUT https://sandbox.facilero.com/api/v1/payments/card/recurring/sub_3344
Content-Type: application/json
Authorization: Bearer <AUTH_TOKEN>
{
"plan": {
"amount": "24.99",
"frequency": "MONTHLY",
"interval": 1
},
"status": "PAUSED"
}4. Cancel Subscription
Endpoint
Live
DELETE https://live.facilero.com/api/v1/payments/card/recurring/{subscriptionId}Sandbox
DELETE https://sandbox.facilero.com/api/v1/payments/card/recurring/{subscriptionId}Request Headers
Authorization: Bearer <AUTH_TOKEN>
Response:
204 No Contenton success
Subscription Webhook Callback DTO
When a scheduled charge attempt occurs, Facilero sends a POST to your callbackUrl. Below is the schema for that payload:
| Field | Type | Required | Description |
|---|---|---|---|
subscriptionId | String | Yes | Identifier of the subscription plan. |
transactionId | String | Yes | Identifier of the individual charge transaction. |
chargeDate | String | Yes | ISO-8601 date when this charge was attempted (e.g. 2025-09-01). |
amount | Number | Yes | Amount attempted in this cycle. |
currency | String | Yes | ISO 4217 currency code for this charge. |
transactionStatus | String | Yes | SUCCEED or FAILED. |
declineCode | Int | No | Scheme-specific decline code, if the charge failed. |
declineReason | String | No | Human-readable decline reason, if the charge failed. |
failureCount | Integer | Yes | Number of consecutive failed attempts on this subscription. |
nextChargeDate | String | Yes | ISO-8601 date of the next scheduled attempt (e.g. 2025-10-01). |
metadata | Map<String, String> | No | Echo of your subscription’s metadata, if provided on create. |
Example JSON Payload
{
"subscriptionId": "sub_3344",
"transactionId": "txn9012",
"chargeDate": "2025-09-01",
"amount": 19.99,
"currency": "EUR",
"transactionStatus":"SUCCEED",
"declineCode": null,
"declineReason": null,
"failureCount": 0,
"nextChargeDate": "2025-10-01",
"metadata": {
"subscriptionName": "PremiumPlan",
"customerId": "cust-789"
}
}Explanation:
This Recurring API leverages your existing data models and flows—simply supply a cardToken and define a plan. Facilero handles scheduling, retries, and webhooks, while you retain full control over subscription lifecycle.
Error Responses
| HTTP Status | Scenario | Example errorMessage |
|---|---|---|
| 400 | Token not found, expired, or inactive | Token not found or expired: tok_xxxx**** |
| 400 | Billing details missing from both request & token | Billing details missing from both request and token |
| 400 | Missing required fields (e.g. callbackUrl, plan) | Missing required field: callbackUrl |
| 400 | Subscription not found | Subscription not found: 12345 |
| 400 | Subscription does not belong to merchant | Subscription does not belong to merchant |
Implementation Notes
POST /v1/payments/card/recurringperforms the first charge immediately (default behavior). The subscription is only created if the charge is not declined (FAILED/BLOCKED). If declined,subscriptionIdandnextChargeDatearenull— retry the request safely- With
skipFirstCharge: true, the subscription is created inTRIALINGstatus with no initial charge. The first charge occurs onplan.startDate. After the first successful charge, the status transitions toACTIVE - Subsequent charges are automatic — the scheduler runs every 5 minutes and processes due subscriptions (
ACTIVEandTRIALING) DELETE /v1/payments/card/recurring/{subscriptionId}returns 204 No Content — immediately cancels the subscription (no more charges)GET /v1/payments/card/recurring/{subscriptionId}— returns current subscription status, plan details, next charge date, failure countPUT /v1/payments/card/recurring/{subscriptionId}— update plan parameters or transition subscription state (ACTIVE, PAUSED, CANCELLED)- After
maxRetries(default 3) consecutive failures, the subscription is automatically paused - When
endDateis reached, the subscription is automatically cancelled - The subscription callback webhook (
SubscriptionCallbackDto) includes realnextChargeDateandfailureCountvalues
Sandbox Testing
Test Card Behavior
| Card Number | Recurring Behavior |
|---|---|
4111111111111111 | All scheduled charges succeed |
5500000000000004 | All scheduled charges succeed |
4000000000000002 | First charge succeeds, subsequent charges decline (simulates card expiry/insufficient funds) |
4000000000003220 | First charge triggers 3DS, subsequent charges succeed (MIT exemption) |
Verifying Scheduled Charges
In sandbox, the recurring scheduler runs every 5 minutes. To verify:
- Create a subscription with
frequency: "DAILY"andstartDateset to today - The first charge happens immediately on
POST /recurring(or is deferred ifskipFirstCharge: true) - The next charge fires within 5 minutes (sandbox accelerates the schedule)
- Check your
callbackUrlfor the webhook, or pollGET /v1/payments/card/recurring/{subscriptionId}to see updatednextChargeDateandfailureCount - For
skipFirstCharge: true, verify the subscription starts asTRIALINGand transitions toACTIVEafter the first scheduled charge
Note: In production, charges fire on the actual scheduled date. The 5-minute sandbox cycle is for testing convenience only.

