Create subscription
| METHOD | URL |
|---|---|
| POST | /subscriptions |
| EXAMPLE | https://test.api.revuppayments.com/v2/subscriptions |
The call to the subscriptions API call has a similar body, response and authentication as the create order.
These are the differences:
Body differences
-
money object: It does not have to be sent because it is defined in the plan
-
currency: New field. Mandatory. This field allows multicurrency for a plan. Currency in which you want the recurrences to be charged.
-
generateToken: It does not have to be sent because it will be always generated
-
notificationUrl: It does not have to be sent because it is replaced by
eventSubscriptionUrl -
parentTransactionId: It does not have to be sent because Revup is going to generate the rebills
-
planId: New field. It must be sent. This is the ID from the previously created Plan in the BO. UUID format
paymentDetails object: It does not have to be sent because it is not necessary. (Note: Mandatory for recurrencies with ApplePay and MPAN)
-
eventSubscriptionUrl: New field. It must be sent. This is the URL where Merchant will receive subscription events
-
overwrite.initialAmount: New field. Optional. If present, the initial subscription amount will be overwritten by the specified amount.
Response differences
- subscriptionId: New field. This is the ID from the new subscription created. UUID format
Request body example - Create new subscription
{
"planId": "4aa7c1e4-cf8c-4d1c-a6ad-451dd07411ee",
"currency": "EUR",
"eventSubscriptionUrl": "https://2ec7-54-73-197-22.ngrok-free.app/events",
"description": "New subscription from merchant",
"concept": "Order concept",
"redirectUrl": {
"success": "www.merchant-domain.es/success",
"fail": "www.merchant-domain.es/failed",
"threeDSChallengeResult": "www.merchant-domain/challenge"
},
"userInformation": {
"ip": "84.102.122.9",
"email": "user@test.com",
"country": "ES",
"language": "ES",
"customerId": "12345678901234567890",
"browserInformation": {
"userAgentHeader": "Mozilla/5.0",
"acceptHeader": "text/html",
"fingerprint": "4sd54dfsafgsadf5"
}
},
"overwrite": {
"initialAmount": 111.99
},
"domain": "https://www.merchant-domain.es"
}
Success responses - Create subscription
| HTTP CODE | DESCRIPTION |
|---|---|
| 200 | The orderId and subscriptionId are provided in the body in JSON format. |
Success response example - Create subscription
{
"orderId": "2989ce95-4309-4f24-a81a-f8dc27c9f20d",
"subscriptionId": "0192b445-55a7-74af-a5d1-79ca6d2310f4"
}
Error responses - Create subscription
| HTTP CODE | ERROR CODE | ERROR MESSAGE | DESCRIPTION |
|---|---|---|---|
| 400 | 0905 | Problem Parsing Request | The request syntax is incorrect. |
| 400 | 0300 | Invalid Request | The request body does not match the JSON schema. |
| 400 | 0301 | Missing or invalid field: %s | Mandatory or format field error. |
| 400 | 0313 | Invalid Country | The provided country is not valid. The code has to comply with ISO 3166. |
| 400 | 0705 | Invalid Format Provided | The provided language is not valid. The code has to comply with ISO 639-1. |
| 400 | 0605 | Invalid IP Address | The provided IP is not valid. The IP has to comply with the IPv4 protocol and be a public address. |
| 400 | 0203 | Duplicated Subscription | The provided purchase ID is already being used in another subscription or transaction with status pending or success. |
| 500 | 0900 | Internal Server Error | The server experienced an unexpected error. |
Subscription creation: idempotency keys
A network timeout leaves you not knowing whether the subscription was created. Retrying blindly can charge your customer twice. The x-idempotency-key header removes that risk.
Without it, the same request sent twice creates two subscriptions and charges twice.
How to use it
- Generate a key before your first attempt. UUID v7 is recommended; any canonical UUID is accepted.
- Send it on the request.
- On a network failure or timeout, retry with the same key.
- Use a new key for every new operation — and whenever you change the body.
POST /subscriptions HTTP/1.1
x-api-key: <your key>
x-idempotency-key: 0192f3a1-8b7c-7def-8123-456789abcdef
Content-Type: application/json
{
"planId": "3f1c9a52-8b0e-4d77-9f31-6c2a5e8d4b10",
"eventSubscriptionUrl": "https://your-domain.example/webhooks/subscriptions",
"tokenId": "tok_8f2c41a9",
"parentOrderId": "ORD-2026-000184",
"parentTransactionId": "9c1b7e40-3a52-4d18-bb6f-2e7c05a91d33"
}
Retrying that exact request with that exact key returns the same subscriptionId and does not charge the card a second time.
The header
| Name | x-idempotency-key |
| Required | No |
| Format | A canonical UUID: 8-4-4-4-12 hexadecimal digits. v7 recommended. |
| Scope | Your account. Two merchants can use the same value without interfering. |
| Lifetime | 24 hours from the first request that used it |
Every response to a request that carried the header echoes it back in x-idempotency-key.
What happens
| Situation | Response |
|---|---|
| First request with a key | Runs normally |
| Retry, same key, same body | The original response, replayed exactly — same subscriptionId, no second charge |
| Retry while the first is still in flight | 409 — back off and retry the same key |
| Same key, different body | 422 — do not retry; the key belongs to the first request |
| Malformed or empty key | 400 |
| Idempotency service unavailable | 503- retry the same key |
Deliberate upsell vs. accidental retry
This is what the key is really for. Creating two subscriptions from the same parent payment is a legitimate operation, and without a key we cannot tell it apart from a retry:
same parentOrderId, two DIFFERENT keys → two subscriptions (deliberate upsell)
same parentOrderId, the SAME key → one subscription (retry, replayed)
same parentOrderId, NO key → two subscriptions (we cannot tell)
Four things worth knowing
Keys live 24 hours. After that the key is forgotten, and reusing it starts a brand new operation — which will create another subscription.
The body is compared, not just the key. Two bodies that mean the same thing match even if the JSON keys are in a different order, the indentation differs, or an amount is written 10.00 instead of 10.0. Any real difference is a 422.
A 400 consumes the key. If your request fails validation, that 400 is stored against the key. Fixing the body and retrying with the same key returns 422, not a fresh attempt. Always use a new key when you change the body.
A replayed 200 still does not mean the payment succeeded. A 200 means the subscription was accepted; the charge happens afterwards. You learn its outcome from the event delivered to your eventSubscriptionUrl, exactly as without the header.
Error codes - Idempotency keys
| HTTP | Code | When | What to do |
|---|---|---|---|
400 | 0060 | The key is not a canonical UUID | Fix the key format |
409 | 0061 | The same key is being processed right now | Back off, retry the same key |
422 | 0062 | The key was already used with a different request | Do not retry; use a new key |
500 | 0063 | A previous attempt with this key never completed; its outcome is unknown | Contact support before retrying |
503 | 0064 | The idempotency service is unavailable | Retry the same key |
All errors share one shape:
{
"error": {
"code": "0062",
"description": "This idempotency key was already used with a different request"
}
}
Checklist
- Generate one key per logical operation, before the first attempt.
- Retry timeouts with the same key; never with a new one.
- Use a new key whenever the body changes.
- Do not retry a
422. - Back off on
409, then retry the same key. - Use two different keys when you deliberately want two subscriptions.
- Keep waiting for the webhook to confirm payment — a replayed
200is not a receipt.