Recurring Payment API

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/recurring

Sandbox

POST https://sandbox.facilero.com/api/v1/payments/card/recurring

Request Headers

  • Content-Type: application/json
  • Authorization: Bearer <AUTH_TOKEN>

Request Body

FieldTypeRequiredDescription
requestIdStringYesUnique identifier for this subscription creation.
midStringYesMerchant account ID.
cardTokenStringYesVaulted token obtained from the Tokenization API.
planObject (Plan)YesSubscription schedule (amount, currency, frequency, interval, dates). See Plan Object below.
billingDetailsObject (BillingDetails)YesCustomer billing/KYC info.
orderObject (Order)YesOrder context to associate with charges.
deviceObject (Device)YesDevice/browser info for risk scoring.
skipFirstChargeBooleanNoDefault 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.
callbackUrlStringYesURL to receive subscription event webhooks (success/failure of each charge attempt).
successRedirectUrlStringYesURL to redirect the customer after successful 3DS verification.
failureRedirectUrlStringYesURL to redirect the customer after failed or cancelled 3DS verification.
metadataMap<String, String>NoFree-form key/value pairs for your own reference.

⚠️

Important — 3D Secure on subscription creation

3DS verification may be initiated when the subscription is created, regardless of the skipFirstCharge flag.

When 3DS is triggered, the response returns:

  • transactionStatus: "CUSTOMER_VERIFICATION"
  • challengeUrl

Redirect the customer to challengeUrl to complete the challenge.

After completion, they are sent to:

  • successRedirectUrl
  • failureRedirectUrl

Both redirect URLs are required on every subscription request.

Plan Object

FieldTypeRequiredDescription
amountStringYesDecimal amount per billing cycle (precision per-currency).
currencyStringYesISO 4217 code (e.g. EUR, USD).
frequencyStringYesBilling cadence. One of: DAILY, WEEKLY, MONTHLY, CUSTOM. CUSTOM charges every interval days — use for non-standard periods (e.g. every 45 days).
intervalIntegerYesMultiplier for frequency. Examples: WEEKLY + 2 = every 2 weeks, MONTHLY + 3 = quarterly, CUSTOM + 45 = every 45 days.
startDateStringYesISO 8601 date when billing begins (e.g. 2025-08-01).
endDateStringNoISO 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.

FieldTypeDescription
subscriptionIdString?Unique identifier for the subscription. null when the first charge was declined (FAILED/BLOCKED) — no subscription is created, and you may retry the request.
transactionIdString?Unique identifier for the first charge transaction. null when skipFirstCharge=true.
transactionStatusString?Status of the first charge: SUCCEED, FAILED, CUSTOMER_VERIFICATION, etc. null when skipFirstCharge=true.
declineCodeInt?Decline code if the first charge failed (nullable).
challengeUrlString?3DS challenge URL the customer must be redirected to when transactionStatus is CUSTOMER_VERIFICATION. null otherwise.
challengeUrlIframeString?Same as challengeUrl but optimized for iframe embedding. null when no 3DS challenge is required.
nextChargeDateString?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/json
  • Authorization: Bearer <AUTH_TOKEN>

Request Body

Include any of the mutable fields from the Plan Object or set:

Request Body DTO

FieldTypeRequiredDescription
plan.amountStringNoUpdate the subscription’s amount per billing cycle (precision per-currency).
plan.currencyStringNoUpdate the ISO 4217 currency code (e.g. EUR, USD).
plan.frequencyStringNoUpdate the billing cadence (DAILY, WEEKLY, MONTHLY, CUSTOM).
plan.intervalIntegerNoUpdate the multiplier for frequency (e.g. 2 + WEEKLY = every 2 weeks).
plan.startDateStringNoChange the ISO 8601 start date (e.g. 2025-08-01).
plan.endDateStringNoChange or add the ISO 8601 end date; omit to leave open-ended.
statusStringNoTransition 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 Content on 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:

FieldTypeRequiredDescription
subscriptionIdStringYesIdentifier of the subscription plan.
transactionIdStringYesIdentifier of the individual charge transaction.
chargeDateStringYesISO-8601 date when this charge was attempted (e.g. 2025-09-01).
amountNumberYesAmount attempted in this cycle.
currencyStringYesISO 4217 currency code for this charge.
transactionStatusStringYesSUCCEED or FAILED.
declineCodeIntNoScheme-specific decline code, if the charge failed.
declineReasonStringNoHuman-readable decline reason, if the charge failed.
failureCountIntegerYesNumber of consecutive failed attempts on this subscription.
nextChargeDateStringYesISO-8601 date of the next scheduled attempt (e.g. 2025-10-01).
metadataMap<String, String>NoEcho 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 StatusScenarioExample errorMessage
400Token not found, expired, or inactiveToken not found or expired: tok_xxxx****
400Billing details missing from both request & tokenBilling details missing from both request and token
400Missing required fields (e.g. callbackUrl, plan)Missing required field: callbackUrl
400Subscription not foundSubscription not found: 12345
400Subscription does not belong to merchantSubscription does not belong to merchant

Implementation Notes

  • POST /v1/payments/card/recurring performs the first charge immediately (default behavior). The subscription is only created if the charge is not declined (FAILED/BLOCKED). If declined, subscriptionId and nextChargeDate are null — retry the request safely
  • With skipFirstCharge: true, the subscription is created in TRIALING status with no initial charge. The first charge occurs on plan.startDate. After the first successful charge, the status transitions to ACTIVE
  • Subsequent charges are automatic — the scheduler runs every 5 minutes and processes due subscriptions (ACTIVE and TRIALING)
  • 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 count
  • PUT /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 endDate is reached, the subscription is automatically cancelled
  • The subscription callback webhook (SubscriptionCallbackDto) includes real nextChargeDate and failureCount values

Sandbox Testing

Test Card Behavior

Card NumberRecurring Behavior
4111111111111111All scheduled charges succeed
5500000000000004All scheduled charges succeed
4000000000000002First charge succeeds, subsequent charges decline (simulates card expiry/insufficient funds)
4000000000003220First charge triggers 3DS, subsequent charges succeed (MIT exemption)

Verifying Scheduled Charges

In sandbox, the recurring scheduler runs every 5 minutes. To verify:

  1. Create a subscription with frequency: "DAILY" and startDate set to today
  2. The first charge happens immediately on POST /recurring (or is deferred if skipFirstCharge: true)
  3. The next charge fires within 5 minutes (sandbox accelerates the schedule)
  4. Check your callbackUrl for the webhook, or poll GET /v1/payments/card/recurring/{subscriptionId} to see updated nextChargeDate and failureCount
  5. For skipFirstCharge: true, verify the subscription starts as TRIALING and transitions to ACTIVE after the first scheduled charge

Note: In production, charges fire on the actual scheduled date. The 5-minute sandbox cycle is for testing convenience only.