Subscriptions
Subscriptions let you bill a customer automatically and recurringly in crypto. You define a Plan (price, currency, billing interval), subscribe a customer to it, and IvoryPay takes care of pulling payment from the customer's wallet on every billing cycle until the subscription is cancelled.
Billing works on a one-time on-chain approval model: the customer approves a token allowance once (via an IvoryPay-hosted checkout page), and IvoryPay charges against that allowance on each due date — no further action from the customer is required for renewals.
Base path: /v1/subscriptions
Authentication: Secret key (Authorization: sk_...)
How the flow works
Create a plan with
POST /v1/subscriptions/plans.Create a subscription for the customer with
POST /v1/subscriptions/create. IvoryPay returns anapprovalUrl.Send the customer to the hosted checkout page at
approvalUrl. The customer connects their wallet and approves the token allowance there.After approval, IvoryPay creates the underlying subscription record and automatically triggers the first charge attempt.
If the first charge succeeds, the subscription becomes
ACTIVE. If it fails, IvoryPay retries for a short activation window and marks the subscriptionNOT_APPROVEDif no charge lands.Once the subscription is
ACTIVE, IvoryPay auto-charges the customer on every billing cycle.Failed renewal charges retry according to the plan's retry policy and fire an
invoice.payment_failedwebhook.You can cancel at any time. Future charges stop after cancellation.
Only step 1 and 2 are things you call directly. Step 3 happens on a page IvoryPay hosts for you (checkout.ivorypay.io) — you never see or handle the customer's wallet or on-chain approval yourself. See Hosted Checkout below for what happens there.
Endpoints
POST
/v1/subscriptions/plans
Create a billing plan
GET
/v1/subscriptions/plans
List plans (paginated)
GET
/v1/subscriptions/plans/:planCode
Get a single plan
PATCH
/v1/subscriptions/plans/:planCode
Update a plan
POST
/v1/subscriptions/create
Start a subscription for a customer
PATCH
/v1/subscriptions/:subCode/cancel
Cancel a subscription
GET
/v1/subscriptions/subscribers
List subscribers (customer + plan pairs)
GET
/v1/subscriptions
List subscriptions
GET
/v1/subscriptions/:id
Get a single subscription by ID
POST /v1/subscriptions/plans
Creates a billing plan. planCode is generated by IvoryPay and returned in the response — you'll use it to subscribe customers.
Body Parameters
planName
string
Yes
Display name for the plan
description
string
No
Free-text description
amountType
"CRYPTO"
Yes
Only CRYPTO is accepted right now.
currency
string
Yes
The fiat currency the plan is priced in, e.g. USD (see note below).
amount
number
Yes
Price per billing cycle, in currency. Must be >= 0.
interval
string
Yes
Billing frequency. One of: HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, BIANNUALLY, YEARLY
Pricing note:
amount/currencyare always priced in a fiat reference currency (e.g.9.99 USD/month). The customer picks the crypto token and network they want to pay with when they complete checkout, and IvoryPay convertsamountinto that token at the prevailing rate on every charge — so the crypto amount pulled can vary slightly cycle to cycle with the exchange rate, while the fiat price stays fixed.
Request
Response
GET /v1/subscriptions/plans
Lists your plans, enriched with subscriber counts and revenue.
Query Parameters
page
number
No
Page number. Defaults to 1.
limit
number
No
Results per page. Defaults to 10, max 100.
q
string
No
Case-insensitive search against planName or planCode.
isActive
boolean
No
Filter by active/inactive plans.
Request
Response
Response Fields
records[].numberOfSubscriptions
number
Total subscriptions ever created on this plan (any status)
records[].totalRevenue
number
Sum of lifetimeValue across all subscriptions on this plan
count
number
Total plans matching the query, across all pages
GET /v1/subscriptions/plans/:planCode
Returns a single plan, including basic business info (shown to customers on the checkout page).
Request
Response
Returns 404 Plan not found if the plan doesn't exist, belongs to another business, or has been deleted.
PATCH /v1/subscriptions/plans/:planCode
Updates a plan. All fields are optional — only what you send is changed.
Body Parameters
planName
string
No
New display name
description
string
No
New description
amount
number
No
New price
interval
string
No
New billing interval
isActive
boolean
No
Set to false to stop new subscribers from subscribing to this plan
updateExistingSubscriptions
boolean
No
If true, cascades a new amount/interval to every active subscriber (see note below)
Existing subscribers: By default, changing
amountorintervalonly affects new subscriptions — subscribers already on the plan keep paying their original price/schedule. PassupdateExistingSubscriptions: trueto roll the change out to every active subscriber. The new amount/interval takes effect on each subscriber's next billing cycle, not immediately — there's no forced re-approval or interruption to the current cycle.
Request
Response
POST /v1/subscriptions/create
Starts a subscription for a customer. This doesn't charge anyone yet — it creates (or reuses) a pending checkout and gives you a URL to send the customer to, where they connect their wallet and approve the on-chain allowance that IvoryPay will bill against.
Body Parameters
email
string
Yes
Customer's email. Used to find-or-create the customer record.
planCode
string
Yes
Code of the plan to subscribe them to. Must be an active plan.
firstName
string
No
Customer's first name
lastName
string
No
Customer's last name
Request
Response
Redirect the customer to approvalUrl (or embed it) to complete setup. Calling this again with the same email + planCode while a checkout is still pending returns the same approvalUrl rather than creating a duplicate.
Errors
400
Plan not found
planCode doesn't exist, or isn't active
PATCH /v1/subscriptions/:subCode/cancel
Cancels a subscription. Only subscriptions in ACTIVE can be cancelled. No further charges are attempted once cancelled — this cannot be undone via the API.
Request
Response
Errors
400
Subscription not found
subCode doesn't exist for this business
400
Subscription is currently not active
Subscription isn't ACTIVE (e.g. CANCELLED, PENDING_APPROVAL, EXPIRED, NOT_APPROVED)
GET /v1/subscriptions/subscribers
Lists your subscribers — one row per subscription, paired with the customer behind it. Useful for a "who's subscribed" view rather than a transaction-style list.
Query Parameters
page
number
No
Page number. Defaults to 1.
limit
number
No
Results per page. Defaults to 10, max 100.
planId
string
No
Filter by plan UUID.
q
string
No
Search by customer email (exact) if it contains @, otherwise by customer name.
Request
Response
startedAt— when the subscription first wentACTIVE— not when the checkout was initiated.
GET /v1/subscriptions
Lists raw subscription records for your business (all statuses by default).
Query Parameters
page
number
No
Page number. Defaults to 1.
limit
number
No
Results per page. Defaults to 10, max 100.
planId
string
No
Filter by plan UUID.
q
string
No
Search by customer email (exact) if it contains @, otherwise by subCode (partial, case-insensitive).
Request
Response
GET /v1/subscriptions/:id
Returns a single subscription by its UUID (id, not subCode).
Request
Response
Returns 404 Subscription not found if id doesn't exist or belongs to another business.
Subscription Status Values
PENDING_APPROVAL
Checkout submitted, but the first on-chain charge hasn't succeeded yet.
ACTIVE
Approved and billing normally — will be auto-charged on each interval.
NOT_APPROVED
The customer never completed (or abandoned) approval within the activation window.
CANCELLED
Cancelled — no further charges will be attempted.
EXPIRED
Subscription lapsed.
Webhooks
Two events fire during the billing lifecycle, on top of the standard payment webhooks. Register for them the same way — see Webhooks.
invoice.create
Every time IvoryPay attempts to charge a subscriber for a billing cycle (including the first activation charge).
invoice.payment_failed
A charge attempt for a subscriber failed.
Payload
failureReason is only populated on invoice.payment_failed. attemptNumber starts at 1 and increments on each retry for the same billing cycle — use it together with reference to de-duplicate retries in your handler.
A failed renewal charge is retried automatically on a delay based on the plan's interval; a failed first charge (subscription still PENDING_APPROVAL) is retried on a short fixed interval until the activation window elapses, after which the subscription is marked NOT_APPROVED and stops retrying.
Hosted Checkout Page
approvalUrl points to checkout.ivorypay.io, an IvoryPay-hosted page that handles wallet connection and the on-chain allowance approval for you. You don't need to build this yourself.
Error Responses
401
Unauthorized
Missing or invalid API key
400
Plan not found
planCode doesn't exist, isn't active, or doesn't belong to your business
400
Subscription not found
subCode doesn't exist, or doesn't match the required status for the action
404
Subscription not found
id doesn't exist, or belongs to another business (on GET /:id)
400
Subscription is currently not active
Attempted to cancel a subscription that isn't ACTIVE
Code Examples
Node.js — Create a plan and subscribe a customer
Node.js — Cancel a subscription
cURL — Create a plan
cURL — Subscribe a customer
Notes
All endpoints are scoped to the authenticated business and environment — use your live key for live data and your test key for test data.
countin list responses reflects the total matching records, not just the current page.A subscription only starts costing the customer anything once they complete approval on the hosted checkout page — creating a subscription via the API is free of side effects on-chain.
A charge won't fire earlier than the subscription's
nextChargeDate.
Last updated