Create Recurring Subscription
Card Tokenization — Create Recurring Subscription
11 min readUpdated Mar 27, 2026
POST /api/v1/payments/card/recurring
Create a recurring subscription tied to a tokenized card. Performs the first charge immediately (unless skipFirstCharge: true). Returns a subscriptionId for subsequent management.
Guide: Recurring Payments — conceptual walkthrough, flow diagrams, and integration patterns.
#Headers
| Header | Value |
|---|---|
Content-Type | application/json |
Authorization | Bearer {token} — see Authentication |
#Request Body Parameters
| Field | Type | Required | Description |
|---|---|---|---|
requestId | String | Yes | Unique identifier for the request. Used for idempotency — if you retry with the same requestId, the system will return the original response instead of processing a duplicate. |
mid | String | Yes | Merchant account ID. Identifies which merchant account the transaction is associated with. |
cardToken | String | Yes | |
plan | Object — See SubscriptionPlan | Yes | |
billingDetails | Object — See BillingDetails | No | |
order | Object — See Order | No | |
device | Object — See Device | No | |
callbackUrl | String | Yes | Server-to-server webhook URL. Exirom sends a POST with the final transaction result to this URL. See Webhook Callbacks. |
successRedirectUrl | String | Yes | URL to redirect the customer after a successful payment or 3D Secure authentication. |
failureRedirectUrl | String | Yes | URL to redirect the customer after a failed payment or 3D Secure authentication. |
skipFirstCharge | Boolean | No | |
metadata | Map<String, String> | No | Arbitrary key-value pairs for your own use (e.g. order reference, campaign ID). Returned unchanged in responses and callbacks. |
| Field | Type | Required | Description |
|---|---|---|---|
amount | String | Yes | Transaction amount as a decimal string in major currency units (e.g. "10.00" = ten dollars). Values with more than two decimal places are automatically rounded. |
currency | String | Yes | Three-letter ISO 4217 currency code (e.g. USD, EUR, GBP). |
frequency | String (DAILY, WEEKLY, MONTHLY, CUSTOM) | Yes | |
interval | Integer | Yes | |
startDate | String | Yes | |
endDate | String | No |
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | String | No | Your internal customer identifier. Useful for linking transactions to user accounts in your system. |
firstName | String | No | Customer's first name. Required for 3DS2 frictionless flow. |
lastName | String | No | Customer's last name. Required for 3DS2 frictionless flow. |
address1 | String | No | Billing street address (line 1). Required for 3DS2 frictionless flow. |
city | String | No | Billing city. Required for 3DS2 frictionless flow. |
state | String | No | Billing state or province. |
country | String | No | Billing country as an ISO 3166-1 alpha-2 code (e.g. US, GB, DE). |
postalCode | String | No | Billing ZIP or postal code. |
phone | String | No | Customer's phone number (including country code). Required for 3DS2 frictionless flow. |
email | String | Yes | Customer's email address. |
dateOfBirth | String | No | Customer's date of birth in YYYY-MM-DD format. Required for 3DS2 frictionless flow. |
| Field | Type | Required | Description |
|---|---|---|---|
date | String | No | Order date in ISO 8601 format (e.g. 2025-07-17). |
orderId | String | No | Your unique order identifier for reconciliation. |
title | String | No | Order title or description (e.g. "Monthly Subscription"). |
siteId | String | No | Identifier for the site or platform where the order originated. |
name | String | No | Customer name associated with the order. |
domainName | String | No | Domain where the order was placed (e.g. "shop.example.com"). |
| Field | Type | Required | Description |
|---|---|---|---|
deviceId | String | No | Unique identifier for the customer's device, if you generate one. |
fingerprintData | String | No | Device fingerprint hash for risk assessment and fraud prevention. |
ip | String | No | Customer's IP address. Used for geolocation and risk scoring. |
accept | String | No | Browser's Accept header value. Required for 3D Secure. |
acceptLanguage | String | No | Browser's Accept-Language header. Required for 3D Secure. |
acceptHeader | String | No | Alternative Accept header field. Required for 3D Secure if accept is not provided. |
userAgent | String | No | Browser's User-Agent string. |
javaEnabled | Boolean | No | Whether Java is enabled in the browser. Required for 3D Secure. |
javaScriptEnabled | Boolean | No | Whether JavaScript is enabled. Required for 3D Secure. |
deviceLanguage | String | No | Device's language setting (e.g. en). Required for 3D Secure. |
colorDepth | String | No | Screen color depth (e.g. "24" for 24-bit). Required for 3D Secure. |
screenHeight | String | No | Screen height in pixels. Required for 3D Secure. |
screenWidth | String | No | Screen width in pixels. Required for 3D Secure. |
deviceTimezone | String | No | Device timezone offset or name (e.g. "America/New_York"). Required for 3D Secure. |
#Response
| Field | Type | Description |
|---|---|---|
subscriptionId | String | |
transactionId | String | Unique transaction identifier assigned by Exirom. Use this ID to query status, retrieve info, or reference the transaction in support requests. |
transactionStatus | String (NEW, PENDING, FAILED, REFUNDED, CUSTOMER_VERIFICATION, ...) | Current status of the transaction. See Transaction Status Guide for the full lifecycle. |
declineCode | Integer | Numeric code indicating the reason for a decline. Only present when the transaction is FAILED. See Decline Codes Reference. |
challengeUrl | String | URL to redirect the customer for 3D Secure authentication. Present when transactionStatus is CUSTOMER_VERIFICATION. |
challengeUrlIframe | String | Embeddable 3D Secure challenge URL for iframe integration. Present when transactionStatus is CUSTOMER_VERIFICATION. |
nextChargeDate | String |
{
"requestId": "req_abc123",
"mid": "merchant_123",
"cardToken": "example_cardToken",
"plan": {
"amount": "100.00",
"currency": "USD",
"frequency": "DAILY",
"interval": 12345,
"startDate": "2025-07-17",
"endDate": "2025-07-17"
},
"billingDetails": {
"externalUserId": "example_externalUserId",
"firstName": "John",
"lastName": "Doe",
"address1": "123 Main St",
"city": "New York",
"state": "NY",
"country": "US",
"postalCode": "10001",
"phone": "+12125551234",
"email": "test@example.com",
"dateOfBirth": "2025-07-17"
},
"order": {
"date": "2025-07-17",
"orderId": "ord_789",
"title": "Product Purchase",
"siteId": "site_001",
"name": "John Doe",
"domainName": "shop.example.com"
},
"device": {
"deviceId": "example_deviceId",
"fingerprintData": "example_fingerprintData",
"ip": "192.168.1.1",
"accept": "text/html,application/json",
"acceptLanguage": "en-US,en;q=0.9",
"acceptHeader": "text/html,application/json",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"javaEnabled": true,
"javaScriptEnabled": true,
"deviceLanguage": "en",
"colorDepth": "24",
"screenHeight": "1080",
"screenWidth": "1920",
"deviceTimezone": "America/New_York"
},
"callbackUrl": "https://yourserver.com/callback",
"successRedirectUrl": "https://yourserver.com/callback",
"failureRedirectUrl": "https://yourserver.com/callback",
"skipFirstCharge": true,
"metadata": {
"key1": "value1"
}
}{
"subscriptionId": "192.168.1.1",
"transactionId": "example_transactionId",
"transactionStatus": "NEW",
"declineCode": 12345,
"challengeUrl": "https://yourserver.com/callback",
"challengeUrlIframe": "https://yourserver.com/callback",
"nextChargeDate": "2025-07-17"
}Idempotency: The
requestIdfield ensures idempotent processing. If you retry a request with the samerequestId, the original response is returned without reprocessing.
#Error Responses
| HTTP Status | Description |
|---|---|
400 | Bad Request — missing or invalid parameters. Check the response body for field-level details. |
401 | Unauthorized — missing, expired, or invalid bearer token. Re-authenticate via POST /api/v1/auth. |
404 | Not Found — the requested resource does not exist. |
500 | Internal Server Error — an unexpected error occurred. Retry with exponential backoff. |
curl -X POST https://sandbox.api.exirom.com/api/v1/payments/card/recurring \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"requestId": "req_abc123",
"mid": "merchant_123",
"cardToken": "example_cardToken",
"plan": {
"amount": "100.00",
"currency": "USD",
"frequency": "DAILY",
"interval": 12345,
"startDate": "2025-07-17",
"endDate": "2025-07-17"
},
"billingDetails": {
"externalUserId": "example_externalUserId",
"firstName": "John",
"lastName": "Doe",
"address1": "123 Main St",
"city": "New York",
"state": "NY",
"country": "US",
"postalCode": "10001",
"phone": "+12125551234",
"email": "test@example.com",
"dateOfBirth": "2025-07-17"
},
"order": {
"date": "2025-07-17",
"orderId": "ord_789",
"title": "Product Purchase",
"siteId": "site_001",
"name": "John Doe",
"domainName": "shop.example.com"
},
"device": {
"deviceId": "example_deviceId",
"fingerprintData": "example_fingerprintData",
"ip": "192.168.1.1",
"accept": "text/html,application/json",
"acceptLanguage": "en-US,en;q=0.9",
"acceptHeader": "text/html,application/json",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"javaEnabled": true,
"javaScriptEnabled": true,
"deviceLanguage": "en",
"colorDepth": "24",
"screenHeight": "1080",
"screenWidth": "1920",
"deviceTimezone": "America/New_York"
},
"callbackUrl": "https://yourserver.com/callback",
"successRedirectUrl": "https://yourserver.com/callback",
"failureRedirectUrl": "https://yourserver.com/callback",
"skipFirstCharge": true,
"metadata": {
"key1": "value1"
}
}'#Try It
SandboxTry it
https://sandbox.api.exirom.com/api
Was this helpful?