Documentation

Subscriptions

Subscriptions let you charge customers any supported ERC-20 token on a fixed interval, minute through yearly, with optional free trials. Create plans in the dashboard or API, share hosted signup links, and let Videpay handle wallet approvals, recurring charges, past_due retries, and customer self-service portals. Recurring revenue, minus the 'did their card expire?' panic spiral.

Need machine-readable request/response bodies for your AI or codebase? Download this page as plain Markdown.

When should I use this?

  • SaaS, memberships, or any product billed on a recurring schedule in a supported token.
  • Customers authorize once; Videpay charges each billing cycle automatically.
  • You want hosted signup and customer portal pages without building billing UI.

Why use Videpay?

  • Dashboard + API parity: everything in Subscriptions tab is available programmatically.
  • Public signup links (pub-*) and customer portal links (portal-*) for self-service.
  • Automatic charge cron with past_due retries; manual charge when you need control.
  • Seven webhook events covering plans, charges, failures, cancellation, and reactivation.

Monthly SaaS with trial

You create a $29/month USDC plan with a 7-day trial, generate a public signup link from the dashboard, and email it to prospects. Customers approve the billing token once on the hosted page. After trial, Videpay charges every 30 days and sends subscription.charged. If a charge fails, status becomes past_due and Videpay retries up to three times before cancellation. Three strikes, then the subscription logs off.

Before you start

  • Merchant registered on-chain for the target chain
  • ERC-20 billing token supported on that chain (native assets are not supported for subscriptions V1)
  • Webhook endpoint or Realtime connection for billing lifecycle events

How subscriptions work

  1. Create a plan with amount, interval, and optional trial.
  2. Share a public signup link: or create subscriptions directly via API for known wallets.
  3. Customer approves token allowance and signs an EIP-712 consent on the hosted signup page.
  4. Videpay creates the on-chain subscription and emits subscription.created.
  5. A background job charges when nextChargeAt is due; successful charges emit subscription.charged.
  6. Failed charges move the subscription to past_due; after retries exhaust, it is cancelled.
  7. Customers manage billing via portal links: pay overdue invoices, cancel, or reactivate.

Dashboard features

Everything in the dashboard maps to the API

Open Dashboard → Subscriptions. The Plans tab covers plan creation, fee quotes, deactivation, and signup links. The Subscribers tab covers listing, filtering, manual subscription creation, cancel, charge, and portal links. Customer portal and signup pages are hosted at signupUrl and portalUrl from link responses.

  1. Plans: create plan, quote protocol fee, deactivate plan, copy public signup link (shared or customer-bound).
  2. Subscribers: filter by status (active, past_due, cancelled), plan, or wallet address.
  3. Subscriber detail: view billing metrics, transaction hashes, next charge, cancel, force charge, copy portal link.
  4. Public signup page: ERC-20 approve + wallet signature flow (same as API public endpoints).
  5. Customer portal: billing history timeline, pay overdue balance, cancel, reactivate cancelled subscriptions.

Quote plan fee

Before creating a plan, preview the protocol fee deducted from each charge. Fees come from the on-chain TokenRegistry Subscription fee bucket.

FieldTypeRequiredDescription
chainstringNoChain scope for the request (e.g. base, polygon). Defaults to your dashboard chain selection. Sandbox resolves to testnets automatically.
assetstringYesAsset symbol to quote (any supported ERC-20 on the chain).
amountdecimal stringYesPlan price to quote before creating the plan.
POST/subscriptions/plans/quote-fee

Preview gross, fee, and net amounts for a plan price.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{02  "asset": "USDC",03  "chain": "base",04  "amount": "29"05}

Create a plan

FieldTypeRequiredDescription
chainstringNoChain scope for the request (e.g. base, polygon). Defaults to your dashboard chain selection. Sandbox resolves to testnets automatically.
namestringYesPlan display name shown on hosted signup pages (max 120 characters).
descriptionstringNoOptional plan description (max 500 characters).
assetstringYesBilling asset symbol. Any ERC-20 token supported on the target chain (e.g. USDC, USDT, EURC). Native assets are not supported.
amountdecimal stringYesRecurring charge amount per interval in human units (e.g. "29" for 29 tokens).
intervalenumYesBilling cadence: minute, hourly, weekly, monthly, or yearly. Intervals use fixed seconds (monthly = 30 days, yearly = 365 days), not calendar-aware.
trialSecondsintegerNoFree trial length in seconds. Takes precedence over trialDays when both are sent.
trialDaysintegerNoFree trial length in days (converted to seconds server-side). Ignored when trialSeconds is provided.
planIduuidNoOptional custom plan UUID. If omitted, Videpay generates one. Must be unique per merchant chain profile.
authorizationobjectNoDashboard-only step-up auth when calling from the browser with a JWT session. API key integrations omit this. Supports pin, otpChallengeId, and twoFactorCode. On sandbox, any emailed OTP code is `000000`.
authorization.pinstringNoTransaction PIN when enabled for your business.
authorization.otpChallengeIduuidNoOTP challenge ID from a prior dashboard auth flow. On sandbox, verify the emailed code with `000000`.
authorization.twoFactorCodestringNoTOTP code when 2FA is enabled.
POST/subscriptions/plans

Create a recurring billing plan. Emits subscription.plan.created.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{02  "name": "Pro Monthly",03  "description": "Full platform access billed monthly in USDC.",04  "asset": "USDC",05  "chain": "base",06  "amount": "29",07  "interval": "monthly",08  "trialDays": 709}

List, retrieve, and deactivate plans

FieldTypeRequiredDescription
includeInactivebooleanNoQuery param on list. Default false: only active plans.
offset / limitintegerNoPagination. limit is 1–100, default 20.
GET/subscriptions/plans?includeInactive=true&limit=20

List plans for the business and chain. Set includeInactive=true to include deactivated plans.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json
GET/subscriptions/plans/pln_7f3a9b2c-4d1e-4a8f-9c0b-1e2d3f4a5b6c

Retrieve a single plan by planId.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json
POST/subscriptions/plans/pln_7f3a9b2c-4d1e-4a8f-9c0b-1e2d3f4a5b6c/deactivate

Deactivate a plan. Existing subscriptions continue; new signups are blocked. Emits subscription.plan.deactivated.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{}

Public signup links

Generate hosted signup URLs customers open in a browser. Links use pub-* access tokens. Share signupUrl from the response: customers never need your API key.

FieldTypeRequiredDescription
chainstringNoChain scope for the request (e.g. base, polygon). Defaults to your dashboard chain selection. Sandbox resolves to testnets automatically.
customerIduuidNoBind the signup link to an existing customer record. When set, reference and metadata can personalize the invite.
referencestringNoYour invite or order reference (max 100). Must be unique per business when used.
metadataobjectNoArbitrary JSON stored on the signup link and copied to the subscription when created.
expiresInDaysintegerNoLink expiry in days (1–3650). Omit for a permanent link. Expired links return an error on public endpoints.
POST/subscriptions/plans/pln_7f3a9b2c-4d1e-4a8f-9c0b-1e2d3f4a5b6c/public-link

Create a shared signup link for any customer, or bind to customerId for personalized invites.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{02  "customerId": ":cus_3b7e9f1a-2c4d-5e6f-7a8b-9c0d1e2f3a4b",03  "reference": "invite-1042",04  "metadata": {05    "campaign": "summer-2026"06  },07  "expiresInDays": 3008}
GET/subscriptions/public/plans/pub-a1b2c3d4e5f6789012345678

Public: load plan details and subscribable availability (no auth).

POST/subscriptions/public/plans/pub-a1b2c3d4e5f6789012345678/prepare

Public: check token balance/allowance and return typed data for wallet approval.

Headers

headers
01Content-Type: application/json

Request body

request.json
01{02  "customerAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"03}
POST/subscriptions/public/plans/pub-a1b2c3d4e5f6789012345678/subscribe

Public: submit wallet signature after approval to create the on-chain subscription.

Headers

headers
01Content-Type: application/json

Request body

request.json
01{02  "customerAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",03  "deadline": 1751925600,04  "signupSignature": "0x..."05}

Create a subscription (merchant API)

Create subscriptions directly when you already know the customer wallet: for example after KYC in your app. The customer still must approve the billing token on-chain before charges succeed.

FieldTypeRequiredDescription
chainstringNoChain scope for the request (e.g. base, polygon). Defaults to your dashboard chain selection. Sandbox resolves to testnets automatically.
planIduuidYesActive plan to subscribe the customer to.
customerAddressaddressYesCustomer wallet that will approve the billing token and sign on-chain subscription consent.
customerIduuidNoOptional CRM customer to attach. Populates subscription.customer in responses and webhooks.
referencestringNoYour subscription reference for reconciliation (unique per business).
metadataobjectNoArbitrary JSON stored on the subscription and included in webhooks.
authorizationobjectNoDashboard-only step-up auth when calling from the browser with a JWT session. API key integrations omit this. Supports pin, otpChallengeId, and twoFactorCode. On sandbox, any emailed OTP code is `000000`.
authorization.pinstringNoTransaction PIN when enabled for your business.
authorization.otpChallengeIduuidNoOTP challenge ID from a prior dashboard auth flow. On sandbox, verify the emailed code with `000000`.
authorization.twoFactorCodestringNoTOTP code when 2FA is enabled.
POST/subscriptions

Create a subscription for a wallet address. Emits subscription.created when confirmed on-chain.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{02  "planId": "pln_7f3a9b2c-4d1e-4a8f-9c0b-1e2d3f4a5b6c",03  "customerAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",04  "customerId": ":cus_3b7e9f1a-2c4d-5e6f-7a8b-9c0d1e2f3a4b",05  "reference": "sub-ref-1042",06  "metadata": {07    "tier": "pro"08  }09}

List, retrieve, cancel, and charge

FieldTypeRequiredDescription
chainstringNoChain scope for the request (e.g. base, polygon). Defaults to your dashboard chain selection. Sandbox resolves to testnets automatically.
statusenumNoQuery filter: active, past_due, or cancelled.
planIduuidNoQuery filter: only subscriptions for this plan.
customerIduuidNoQuery filter: subscriptions linked to this CRM customer.
customerAddressaddressNoQuery filter: subscriptions for a specific wallet.
authorizationobjectNoDashboard-only step-up auth when calling from the browser with a JWT session. API key integrations omit this. Supports pin, otpChallengeId, and twoFactorCode. On sandbox, any emailed OTP code is `000000`.
authorization.pinstringNoTransaction PIN when enabled for your business.
authorization.otpChallengeIduuidNoOTP challenge ID from a prior dashboard auth flow. On sandbox, verify the emailed code with `000000`.
authorization.twoFactorCodestringNoTOTP code when 2FA is enabled.
GET/subscriptions?status=active&limit=20

List subscriptions. Filter by status (active, past_due, cancelled), planId, customerId, or customerAddress.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json
GET/subscriptions/sub_5c8d2e1f-9a3b-4c7d-8e6f-1a2b3c4d5e6f

Retrieve a subscription. Active and past_due records are reconciled with on-chain state before returning.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json
POST/subscriptions/sub_5c8d2e1f-9a3b-4c7d-8e6f-1a2b3c4d5e6f/cancel

Merchant-initiated cancel (relayer-signed on-chain). Emits subscription.cancelled.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{}
POST/subscriptions/sub_5c8d2e1f-9a3b-4c7d-8e6f-1a2b3c4d5e6f/charge

Force a charge when the subscription is due. Fails if cancelled or not yet due unless forced by lifecycle rules.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{}

Customer portal links

Portal links (portal-*) let customers view billing history, pay overdue balances, cancel, and reactivate: without your API key. portalUrl points to https://videpay.io/subscriptions/{code}.

FieldTypeRequiredDescription
chainstringNoChain scope for the request (e.g. base, polygon). Defaults to your dashboard chain selection. Sandbox resolves to testnets automatically.
expiresInDaysintegerNoPortal link expiry in days (1–3650). Omit for a permanent link. Re-calling the endpoint refreshes expiry when provided.
POST/subscriptions/sub_5c8d2e1f-9a3b-4c7d-8e6f-1a2b3c4d5e6f/public-link

Generate or refresh a customer portal link for a subscription.

Headers

headers
01x-api-key: sk_AbCdEfGhIj_XyZ1234567890abcdefghijklmnopqrstuvwxyz01234502Content-Type: application/json

Request body

request.json
01{02  "expiresInDays": 3003}
GET/subscriptions/public/portal-f9e8d7c6b5a4321098765432

Public: load subscription status, billing history, reactivation availability, and portal actions.

Customer portal actions (public)

Customers interact with these endpoints from the hosted portal after connecting a wallet. Each flow follows prepare → sign/submit.

POST/subscriptions/public/portal-f9e8d7c6b5a4321098765432/pay/prepare

Prepare token approval and paySubscription contract call for overdue balance.

POST/subscriptions/public/portal-f9e8d7c6b5a4321098765432/pay

Submit payment after approval: forces a charge job.

POST/subscriptions/public/portal-f9e8d7c6b5a4321098765432/cancel/prepare

Return EIP-712 typed data for customer-signed cancellation.

POST/subscriptions/public/portal-f9e8d7c6b5a4321098765432/cancel

Submit cancelSignature to cancel on-chain.

Request body

request.json
01{02  "deadline": 1751925600,03  "cancelSignature": "0x..."04}
POST/subscriptions/public/portal-f9e8d7c6b5a4321098765432/reactivate/prepare

Prepare allowance + typed data to resume a cancelled subscription.

Request body

request.json
01{02  "customerAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"03}
POST/subscriptions/public/portal-f9e8d7c6b5a4321098765432/reactivate

Submit reactivateSignature after approval. Reactivation is portal-only, there is no merchant POST /reactivate endpoint.

Request body

request.json
01{02  "customerAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",03  "deadline": 1751925600,04  "reactivateSignature": "0x..."05}

Billing lifecycle and past_due

No pause: only active, past_due, and cancelled

Subscriptions cannot be paused. A failed charge moves status to past_due. Videpay retries up to three times with exponential backoff. After retries exhaust, the subscription is cancelled automatically. Use manual charge or customer portal pay to recover past_due subscriptions before cancellation.

  1. nextChargeAt: when the next automatic charge is scheduled.
  2. lastChargedAt / lastChargeFailedAt: timestamps for the most recent success or failure.
  3. metrics.chargeAttemptCount / successfulChargeCount: billing attempt counters in API responses.
  4. metrics.onchainRetryCount: past_due retry attempts against the smart contract.
  5. Automatic charges run every minute via background jobs; merchant charge and portal pay trigger immediate attempts.

Subscription object reference

Every subscription.* webhook and GET response uses this shape (via serializeSubscription). The customer field is always present: null when no customerId was provided at signup.

FieldTypeRequiredDescription
iduuidNoVidepay subscription record ID.
subscriptionHashbytes32NoOn-chain subscription identifier hash.
customerAddressaddressNoWallet charged each billing cycle.
customerobject | nullNoLinked CRM customer when customerId was set at creation or checkout collected buyer identity. Always present on deposit sessions, settlement payments, and subscriptions: null when unlinked. Shape: { id, name, email, phoneNumber, reference }.
iduuidNoVidepay customer record ID.
namestringNoDisplay name.
email / phoneNumberstring | nullNoContact fields when collected.
referencestring | nullNoYour CRM or user ID: unique per business.
reference / metadatastring / objectNoYour reconciliation fields: echoed in webhooks.
statusenumNoactive, past_due, or cancelled.
billing.amountRaw / billing.amountstringNoRecurring charge in raw and formatted units.
billing.interval / intervalSecondsstring / integerNoBilling cadence label and fixed-second interval.
nextChargeAtdatetimeNoNext scheduled charge. null when cancelled.
transaction.*TxHashstringNoOn-chain transaction hashes for create, last charge, and cancellation.
planobjectNoEmbedded serialized plan at time of response.

Webhooks you'll receive

Register a webhook endpoint and verify the X-Protocol-Signature header on every delivery before fulfilling orders or updating your database.

Webhooksubscription.plan.created

Sent when you create a subscription plan.

Delivery headers

webhook-headers
01Content-Type: application/json02User-Agent: Protocol-Webhooks/1.003X-Protocol-Event: subscription.plan.created04X-Protocol-Delivery: wev_1a2b3c4d-5e6f-7890-abcd-ef123456789005X-Protocol-Business-Id: a1b2c3d4-e5f6-7890-abcd-ef123456789006X-Protocol-Attempt: 107X-Protocol-Signature: sha256=abc123def456...
Webhooksubscription.plan.deactivated

Sent when you deactivate a plan (existing subscriptions are not deleted).

Delivery headers

webhook-headers
01Content-Type: application/json02User-Agent: Protocol-Webhooks/1.003X-Protocol-Event: subscription.plan.deactivated04X-Protocol-Delivery: wev_1a2b3c4d-5e6f-7890-abcd-ef123456789005X-Protocol-Business-Id: a1b2c3d4-e5f6-7890-abcd-ef123456789006X-Protocol-Attempt: 107X-Protocol-Signature: sha256=abc123def456...
Webhooksubscription.created

Sent when a subscription is created on-chain (merchant API or public signup).

Delivery headers

webhook-headers
01Content-Type: application/json02User-Agent: Protocol-Webhooks/1.003X-Protocol-Event: subscription.created04X-Protocol-Delivery: wev_1a2b3c4d-5e6f-7890-abcd-ef123456789005X-Protocol-Business-Id: a1b2c3d4-e5f6-7890-abcd-ef123456789006X-Protocol-Attempt: 107X-Protocol-Signature: sha256=abc123def456...
Webhooksubscription.charged

Sent when a recurring charge succeeds and the billing window advances.

Delivery headers

webhook-headers
01Content-Type: application/json02User-Agent: Protocol-Webhooks/1.003X-Protocol-Event: subscription.charged04X-Protocol-Delivery: wev_1a2b3c4d-5e6f-7890-abcd-ef123456789005X-Protocol-Business-Id: a1b2c3d4-e5f6-7890-abcd-ef123456789006X-Protocol-Attempt: 107X-Protocol-Signature: sha256=abc123def456...
Webhooksubscription.past_due

Sent when a charge fails and the subscription enters past_due.

Delivery headers

webhook-headers
01Content-Type: application/json02User-Agent: Protocol-Webhooks/1.003X-Protocol-Event: subscription.past_due04X-Protocol-Delivery: wev_1a2b3c4d-5e6f-7890-abcd-ef123456789005X-Protocol-Business-Id: a1b2c3d4-e5f6-7890-abcd-ef123456789006X-Protocol-Attempt: 107X-Protocol-Signature: sha256=abc123def456...
Webhooksubscription.cancelled

Sent when a subscription is cancelled by merchant, customer portal, or max retries.

Delivery headers

webhook-headers
01Content-Type: application/json02User-Agent: Protocol-Webhooks/1.003X-Protocol-Event: subscription.cancelled04X-Protocol-Delivery: wev_1a2b3c4d-5e6f-7890-abcd-ef123456789005X-Protocol-Business-Id: a1b2c3d4-e5f6-7890-abcd-ef123456789006X-Protocol-Attempt: 107X-Protocol-Signature: sha256=abc123def456...
Webhooksubscription.reactivated

Sent when a cancelled or past_due subscription becomes active again.

Delivery headers

webhook-headers
01Content-Type: application/json02User-Agent: Protocol-Webhooks/1.003X-Protocol-Event: subscription.reactivated04X-Protocol-Delivery: wev_1a2b3c4d-5e6f-7890-abcd-ef123456789005X-Protocol-Business-Id: a1b2c3d4-e5f6-7890-abcd-ef123456789006X-Protocol-Attempt: 107X-Protocol-Signature: sha256=abc123def456...