Skip to content

Merchant API - Subscriptions

Overview

The Subscriptions API lets you create on-chain recurring billing plans, enroll subscribers, and manage subscription links, payments, and invitations for recurring cryptocurrency payments. Billing terms (amount, interval, trial period, grace period) are anchored on-chain via a signed plan digest; a subscription link represents an individual subscriber's enrollment and on-chain approval to be billed.

Base URL: https://sandboxapi.bitxpay.com/api/v1

The API is split into two route groups with different authentication requirements:

GroupPath PrefixesAuthentication
Authenticated (Merchant) Routes/subscriptions/plans, /subscriber, /invitations, /subscriptions/linkMerchant API Key + Ed25519 (EdDSA) signature — same scheme as the Payments API
Public Routes/public/subscriptions/plans, /public/subscriber, /public/subscriptions/link, /public/subscriptions/nftNo merchant signature — called directly from the customer-facing wallet/checkout flow

Plan Billing Models

When creating a subscription plan, choose one of two billing models:

  • PlanByAmount — fixed billing in a specific token: provide token + amount
  • PlanByValue — fixed billing denominated in a currency: provide currency + value

Provide one pair or the other, not both, in the Create Subscription Plan request. The kind field (SubscriptionPlanType) further classifies the plan.


Subscription Plan Statuses

StatusDescription
activePlan is live and can accept new subscribers
deprecatedPlan no longer accepts new subscribers (existing subscribers unaffected)

Subscriber Statuses

StatusDescription
invitation_sentSubscriber created, awaiting on-chain confirmation
activeSubscriber is actively being billed
inactiveSubscriber is not currently active
suspendedSubscriber billing suspended
cancelledSubscription cancelled

This is the canonical subscriber status set referenced by every subscriber endpoint on this page.

Billing Intervals

Plans store interval, trial_period, grace_period (max_arrears), auto_cancel_after_missed, and subscribe_deadline as raw seconds on-chain. Common interval values:

NameSeconds
Daily86400
Weekly604800
Monthly2592000
Quarterly7776000
Yearly31536000

Authenticated (Merchant) Routes

All endpoints in this section require Merchant API Key authentication with an Ed25519 (EdDSA) request signature (RSA-PSS accepted for legacy keys):

X-API-Key: btxm_test_xxxxxxxxxxxx
X-API-Signature: <base64_encoded_ed25519_signature>
X-API-Timestamp: 2026-01-31T12:00:00Z
Content-Type: application/json

Subscription Plans — /subscriptions/plans

1. Create Subscription Plan

POST /subscriptions/plans/

Request Body:

FieldTypeRequiredDescription
network_idstring (UUID)NoBlockchain network ID
currency_idstring (UUID)NoCurrency ID
merchantstringYesMerchant on-chain address
destinationstringYesDestination address for collected funds
tokenstringConditionalToken contract address (required with amount for PlanByAmount)
amountstringConditionalBilling amount, raw units (required with token)
currencystringConditionalCurrency code (required with value for PlanByValue)
valuestringConditionalBilling value in currency (required with currency)
periodintegerNoBilling period (seconds)
intervalintegerNoBilling interval (seconds)
trial_periodintegerNoTrial period (seconds)
max_arrearsintegerNoGrace period before a missed payment counts as arrears (seconds)
auto_cancel_after_missedintegerNoNumber of missed payments before auto-cancellation
subscribe_deadlineintegerNoDeadline for the subscriber to complete on-chain enrollment (seconds)
plan_nonceintegerNoOn-chain plan nonce
merchant_sigstringYesMerchant's on-chain signature over the plan digest
metadata_uristringNoURI to off-chain plan metadata
namestringYesPlan name
descriptionstringNoPlan description
kindstring (SubscriptionPlanType)NoPlan type classification

Provide either token + amount (PlanByAmount) or currency + value (PlanByValue) — not both.

Response (SubscriptionPlanResponse):

json
{
  "id": "uuid",
  "merchant_id": "uuid",
  "network_id": "uuid",
  "chain_id": 0,
  "network": { "id": "uuid", "name": "string", "chain_id": 0, "symbol": "string", "is_active": true, "created_at": "", "updated_at": "" },
  "merchant_address": "string",
  "destination_address": "string",
  "token": "string",
  "currency_id": "uuid",
  "currency": "string",
  "currency_symbol": "string",
  "amount": "string",
  "formatted_amount": "string",
  "interval": 0,
  "trial_period": 0,
  "max_arrears": 0,
  "auto_cancel_after_missed": 0,
  "subscribe_deadline": 0,
  "plan_nonce": 0,
  "plan_id": "string",
  "digest": "string",
  "revoked": false,
  "source": "string",
  "metadata_uri": "string",
  "name": "string",
  "description": "string",
  "is_deprecated": false,
  "deprecated_at": "",
  "status": "active",
  "total_subscribers": 0,
  "active_subscribers": 0,
  "trial_subscribers": 0,
  "failed_subscribers": 0,
  "subscribers": [],
  "created_at": "",
  "updated_at": ""
}

Error Responses:

StatusErrorDescription
400Bad RequestInvalid request payload, or both/neither of token+amount / currency+value provided
401UnauthorizedMissing or invalid API key/signature
500Internal Server ErrorServer error

2. List Subscription Plans

GET /subscriptions/plans/

Query Parameters:

ParameterTypeDescription
pageintegerPage number
page_sizeintegerResults per page
searchstringSearch by plan name
deprecatedbooleanFilter by deprecated status
start_datedatetimeFilter by creation date range start
end_datedatetimeFilter by creation date range end

Response (SubscriptionPlanListResponse):

json
{
  "plans": [ { "...": "SubscriptionPlanResponse, see #1" } ],
  "total": 0,
  "page": 0,
  "page_size": 0,
  "total_pages": 0
}

3. Get Subscription Plan

GET /subscriptions/plans/:id

Path Parameters: id (UUID)

Response: SubscriptionPlanResponse (same shape as #1)

Error Responses:

StatusErrorDescription
401UnauthorizedMissing or invalid API key/signature
404Not FoundPlan not found
500Internal Server ErrorServer error

4. Update Subscription Plan

PUT /subscriptions/plans/:id

Path Parameters: id (UUID)

Request Body:

FieldTypeRequiredDescription
namestringNoUpdated plan name
metadata_uristringNoUpdated metadata URI
statusstring (SubscriptionPlanStatus)Noactive or deprecated

Response: SubscriptionPlanResponse (same shape as #1)

Error Responses:

StatusErrorDescription
400Bad RequestInvalid request payload
401UnauthorizedMissing or invalid API key/signature
404Not FoundPlan not found
500Internal Server ErrorServer error

5. Deprecate Subscription Plan

DELETE /subscriptions/plans/:id

Path Parameters: id (UUID)

Request Body: none

Response: Success message confirming the plan was deprecated.

Error Responses:

StatusErrorDescription
401UnauthorizedMissing or invalid API key/signature
404Not FoundPlan not found
500Internal Server ErrorServer error

Subscribers — /subscriber

6. Create Subscriber

POST /subscriber/

Request Body:

FieldTypeRequiredDescription
subscription_plan_idstring (UUID)YesPlan to subscribe to
customer_idstringNoYour internal customer identifier
customer_namestringNoCustomer's full name
customer_emailstringNoCustomer's email address
webhook_metadataobjectNoCustom metadata passed to webhooks
failure_return_urlstringNoURL to redirect to on failed enrollment
success_return_urlstringNoURL to redirect to on successful enrollment

Response (SubscriberResponse):

json
{
  "id": "uuid",
  "subscription_plan_id": "uuid",
  "merchant_user_id": "uuid",
  "customer_id": "string",
  "customer_name": "string",
  "customer_email": "string",
  "subscription_link_url": "string",
  "status": "invitation_sent",
  "subscription_plan": { "...": "SubscriptionPlan model" },
  "subscription_link": { "...": "SubscriptionLink model" },
  "webhook_metadata": {},
  "created_at": "",
  "updated_at": ""
}

Error Responses:

StatusErrorDescription
400Bad RequestInvalid request payload or missing required fields
401UnauthorizedMissing or invalid API key/signature
404Not FoundSubscription plan ID not found
500Internal Server ErrorServer error

7. Get Subscribers By Merchant

GET /subscriber/

Query Parameters:

ParameterTypeDescription
plan_idstring (UUID)Filter by subscription plan
searchstringSearch by customer name/email
start_datedatetimeFilter by creation date range start
end_datedatetimeFilter by creation date range end
pageintegerPage number (default 1)
page_sizeintegerResults per page (default 10)

Response (SubscriberListResponse):

json
{
  "subscribers": [ { "...": "SubscriberResponse, see #6" } ],
  "data": [ { "...": "Subscriber model" } ],
  "total": 0,
  "page": 0,
  "page_size": 0,
  "total_pages": 0
}

8. Get Subscribers By Plan ID

GET /subscriber/plan/:plan_id

Path Parameters: plan_id (UUID)

Response: SubscriberListResponse (same shape as #7)


9. Update Subscriber

PUT /subscriber/:id

Path Parameters: id (UUID)

Request Body:

FieldTypeRequiredDescription
customer_namestringNoUpdated customer name
customer_emailstringNoUpdated customer email
statusstring (SubscriberStatus)NoNew subscriber status

Response: SubscriberResponse (same shape as #6)

Error Responses:

StatusErrorDescription
400Bad RequestInvalid request payload
401UnauthorizedMissing or invalid API key/signature
404Not FoundSubscriber not found
500Internal Server ErrorServer error

10. Get Subscriber By ID

GET /subscriber/:id

Path Parameters: id (UUID)

Response: SubscriberResponse (same shape as #6)

Error Responses:

StatusErrorDescription
401UnauthorizedMissing or invalid API key/signature
404Not FoundSubscriber not found
500Internal Server ErrorServer error

Invitations — /invitations

11. Get Invitations By Plan ID

GET /invitations/

Query Parameters: plan_id (string, UUID)

Response (InvitationListResponse):

json
{
  "invitations": [ { "...": "SubscriberResponse, see #6" } ],
  "data": [ { "...": "Subscriber model" } ],
  "total": 0,
  "page": 0,
  "page_size": 0,
  "total_pages": 0
}

GET /subscriptions/link/

Query Parameters:

ParameterTypeDescription
pageintegerPage number
page_sizeintegerResults per page
searchstringSearch term
is_activebooleanFilter by active status
plan_idstring (UUID)Filter by subscription plan
start_datedatetimeFilter by creation date range start
end_datedatetimeFilter by creation date range end

Response (SubscriptionLinkListResponse):

json
{
  "links": [ { "...": "SubscriptionLinkResponse, see #13" } ],
  "total": 0,
  "page": 0,
  "page_size": 0,
  "total_pages": 0
}

GET /subscriptions/link/:id

Path Parameters: id (UUID)

Response (SubscriptionLinkResponse):

json
{
  "id": "uuid",
  "merchant_user_id": "uuid",
  "subscription_plan_id": "uuid",
  "subscribed_at": "",
  "trial_ends_at": "",
  "wallet_address": "string",
  "subscribe_hash": "string",
  "approvals": [ { "...": "ApprovalDetail" } ],
  "owed": "string",
  "paid_periods": 0,
  "locked": false,
  "customer_id": "string",
  "customer_email": "string",
  "customer_name": "string",
  "expires_at": "",
  "is_active": true,
  "subscription_transactions": [ { "...": "SubscriptionResponse, see #21" } ],
  "plan": { "...": "SubscriptionPlanSummary" },
  "created_at": "",
  "updated_at": ""
}

Public Routes

The endpoints in this section are called directly from the customer-facing wallet/checkout flow and do not require X-API-Key/X-API-Signature headers. Record Subscription Payment instead identifies the merchant via a merchant_key field in the request body.

Subscription Plans — /public/subscriptions/plans

14. List Public Subscription Plans

GET /public/subscriptions/plans/

Response: SubscriptionPlanListResponse (same shape as #2)


15. Get Public Subscription Plan

GET /public/subscriptions/plans/:id

Path Parameters: id (UUID)

Response: SubscriptionPlanResponse (same shape as #1)


Subscribers — /public/subscriber

16. Prepare Subscription

GET /public/subscriber/prepare

Returns the on-chain parameters (plan digest, approval details, etc.) a customer's wallet needs to sign in order to complete enrollment for a given subscription plan.


17. Get Subscriber By ID

GET /public/subscriber/:id

Path Parameters: id (UUID)

Response: SubscriberResponse (same shape as #6)


18. Update Subscriber

PUT /public/subscriber/:id

Path Parameters: id (UUID)

Request Body: UpdateSubscriberRequest (same shape as #9)

Response: SubscriberResponse (same shape as #6)


POST /public/subscriptions/link/

Request Body:

FieldTypeRequiredDescription
subscription_plan_idstring (UUID)YesPlan being subscribed to
subscriber_idstring (UUID)NoAssociated subscriber ID
subscription_onchain_idintegerNoOn-chain subscription ID
subscribed_attimestampNoSubscription start time
trial_ends_attimestampNoTrial end time
wallet_addressstringYesCustomer's wallet address
subscribe_hashstringYesOn-chain subscription transaction hash
owedstringNoAmount currently owed
paid_periodsintegerNoNumber of billing periods paid
lockedbooleanNoWhether the link is locked from further updates
approvalsarrayYesOn-chain token approval details (at least 1 required)
customer_idstringNoYour internal customer identifier
customer_emailstringNoCustomer's email address
customer_namestringNoCustomer's full name
expires_attimestampNoLink expiration time

approvals[] fields:

FieldTypeRequiredDescription
approval_hashstringNoOn-chain approval transaction hash
token_addressstringYesApproved token contract address
amount_approvedstringYesApproved amount (raw units)
network_namestringYesBlockchain network name
chain_idintegerNoChain ID
decimalsintegerNoToken decimals

Response: SubscriptionLinkResponse (same shape as #13)


GET /public/subscriptions/link/:walletAddress

Path Parameters: walletAddress (string)

Response: SubscriptionLinkListResponse (same shape as #12)


21. Record Subscription Payment

POST /public/subscriptions/link/payment

Request Body:

FieldTypeRequiredDescription
sub_link_idstring (UUID)YesSubscription link ID
merchant_keystringYesMerchant identifier used to authenticate this request
tx_hashstringYesBlockchain transaction hash
block_numberintegerNoBlock number the transaction was included in
block_hashstringNoBlock hash
network_idstring (UUID)YesBlockchain network ID
currency_idstring (UUID)YesCurrency ID
amount_paidstringYesAmount paid
wallet_usedstringYesWallet address the payment was made from

Response (SubscriptionResponse):

json
{
  "id": "uuid",
  "merchant_id": "uuid",
  "sub_link_id": "uuid",
  "tx_hash": "string",
  "block_number": 0,
  "block_hash": "string",
  "confirmations": 0,
  "currency_id": "uuid",
  "network_id": "uuid",
  "amount": "string",
  "user_wallet_address": "string",
  "paid_at": "",
  "status": "string",
  "starts_at": "",
  "ends_at": "",
  "next_billing_at": "",
  "plan": { "...": "SubscriptionLinkResponse, see #13" },
  "created_at": "",
  "updated_at": ""
}

PUT /public/subscriptions/link/:id

Path Parameters: id (UUID)

Request Body:

FieldTypeRequiredDescription
trial_ends_attimestampNoUpdated trial end time
customer_emailstringNoUpdated customer email
customer_namestringNoUpdated customer name
expires_attimestampNoUpdated expiration time
is_activebooleanNoUpdated active status
lockedbooleanNoUpdated locked status
owedstringNoUpdated amount owed

Response: SubscriptionLinkResponse (same shape as #13)


NFT Metadata — /public/subscriptions/nft

23. Get Subscription NFT Metadata

GET /public/subscriptions/nft/:plan_id

Path Parameters: plan_id (UUID)

Response (SubscriptionMetaResponse):

json
{
  "name": "string",
  "description": "string",
  "image": "string",
  "external_url": "string",
  "attributes": [
    { "trait_type": "string", "value": "string" }
  ]
}

Rate Limiting

Rate limits are applied per route based on the configured rate limiter (payment-related routes use a stricter limiter than read/default routes). Contact your account team for the current per-route limits applicable to your account.


Common Use Cases

Create a Subscription Plan

bash
curl -X POST https://sandboxapi.bitxpay.com/api/v1/subscriptions/plans/ \
  -H "X-API-Key: btxm_test_xxxxxxxxxxxx" \
  -H "X-API-Signature: <base64_encoded_ed25519_signature>" \
  -H "X-API-Timestamp: 2026-01-31T12:00:00Z" \
  -H "Content-Type: application/json" \
  -d '{
    "merchant": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3",
    "destination": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3",
    "token": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
    "amount": "100000",
    "interval": 2592000,
    "merchant_sig": "<merchant_onchain_signature>",
    "name": "Pro Plan Monthly"
  }'

Create a Subscriber

bash
curl -X POST https://sandboxapi.bitxpay.com/api/v1/subscriber/ \
  -H "X-API-Key: btxm_test_xxxxxxxxxxxx" \
  -H "X-API-Signature: <base64_encoded_ed25519_signature>" \
  -H "X-API-Timestamp: 2026-01-31T12:00:00Z" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription_plan_id": "6f03697c-eb8d-49f2-9ac1-bca1cbe58a4c",
    "customer_id": "cust-001",
    "customer_email": "customer@example.com",
    "customer_name": "John Doe"
  }'
bash
curl -X POST https://sandboxapi.bitxpay.com/api/v1/public/subscriptions/link/ \
  -H "Content-Type: application/json" \
  -d '{
    "subscription_plan_id": "6f03697c-eb8d-49f2-9ac1-bca1cbe58a4c",
    "wallet_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3",
    "subscribe_hash": "0xa1b2c3d4e5f60789a1b2c3d4e5f60789a1b2c3d4e5f60789a1b2c3d4e5f60781",
    "approvals": [
      {
        "token_address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
        "amount_approved": "100000",
        "network_name": "arbitrum",
        "chain_id": 42161,
        "decimals": 6
      }
    ]
  }'

Record a Subscription Payment (Public)

bash
curl -X POST https://sandboxapi.bitxpay.com/api/v1/public/subscriptions/link/payment \
  -H "Content-Type: application/json" \
  -d '{
    "sub_link_id": "5d17b0c4-b32c-40dd-a2e2-8ace612251c7",
    "merchant_key": "btxm_test_xxxxxxxxxxxx",
    "tx_hash": "0xa1b2c3d4e5f60789a1b2c3d4e5f60789a1b2c3d4e5f60789a1b2c3d4e5f60785",
    "network_id": "0b4f3bab-4354-4373-9821-c8665fffbca3",
    "currency_id": "d4e5f6a7-8b9c-4d1e-9f2a-3b4c5d6e7f80",
    "amount_paid": "49.99",
    "wallet_used": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb3"
  }'

Webhooks

Subscription events can be delivered via webhooks. Configure your webhook URL in the dashboard to receive:

  • subscription.created - New subscription link created
  • subscription.activated - Subscription activated
  • subscription.paused - Subscription paused
  • subscription.cancelled - Subscription cancelled
  • subscription.payment.success - Payment successful
  • subscription.payment.failed - Payment failed
  • subscription.expiring - Subscription nearing expiration

See the Webhooks Guide for detailed payload structures.


Support

For questions or issues: