Skip to main content

Subscriptions

Set up recurring billing, manage subscription plans, and handle payment lifecycle with the PayConnect Subscriptions API.

Overview​

PayConnect subscriptions automate recurring billing with built-in support for:

  • Flexible billing cycles — daily, weekly, biweekly, monthly, quarterly, semiannual, or yearly
  • Subscription plans — pre-configured pricing templates
  • Add-ons and discounts — customize pricing per subscriber
  • Automated dunning — retry failed payments with configurable schedules
  • Manual payments — record offline payments (checks, cash, money orders)
  • Customer self-service — hosted management page for payment method updates

Create a Subscription Plan​

Before creating subscriptions, define a plan. A plan has a single billingCycle and a single price (in dollars); subscriptions inherit the plan's cycle.

curl -X POST https://dev.payconnect.us/api/subscriptions/v2/plans \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "Professional Plan",
"billingCycle": "monthly",
"price": 99.00,
"description": "Full access to all professional features"
}'

Plan Request Fields​

FieldTypeRequiredDescription
namestringYesPlan display name
billingCyclestringYesThe single cycle this plan bills on: daily, weekly, biweekly, monthly, quarterly, semiannual, or yearly. Subscriptions inherit it.
pricenumberYesBase price in dollars charged each billing cycle (e.g., 99.00)
descriptionstringNoPlan description
planIdstringNoCustom plan ID (auto-generated if omitted)
setupFeenumberNoOne-time setup fee in dollars charged on the first invoice only
trialDaysnumberNoNumber of trial days
featuresarrayNoList of plan features
activebooleanNoWhether the plan is active (default: true)
preRenewalInvoiceobjectNoRenewal-invoice timing: { "leadTimeDays": <0 to cycle length> }. See Renewal invoice timing.

Renewal invoice timing​

Every billing cycle, PayConnect automatically generates the renewal invoice and charges it on the renewal date. preRenewalInvoice is a container object whose only field is leadTimeDays — it isn't a second, separate setting. leadTimeDays controls how many days before the renewal date that invoice is issued, so the customer gets advance notice of the upcoming charge.

  • Default: 7 days.
  • Range: 0 up to the billing-cycle length — the per-cycle maximums are 1 (daily), 7 (weekly), 14 (biweekly), 30 (monthly), 90 (quarterly), 182 (semiannual), and 365 (yearly). The value is capped to that length — e.g. a weekly plan issues at most 7 days early.
  • 0 means issue the invoice on the renewal date itself (no advance notice).

Automatic renewal billing always happens — this setting only changes when the invoice is issued, never whether the customer is billed. There is no way to switch renewal billing off for a plan.

{ "name": "Pro", "billingCycle": "monthly", "price": 20.00, "preRenewalInvoice": { "leadTimeDays": 3 } }

List Plans​

curl -X GET "https://dev.payconnect.us/api/subscriptions/v2/plans?active=true" \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID"

Create a Subscription​

The customer must already exist — create one first via the Customers API and pass its customerId.

curl -X POST https://dev.payconnect.us/api/subscriptions/v2 \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"customerId": "cust_abc123",
"planId": "plan_xyz789",
"startDate": "2026-04-01"
}'

Response (201 Created):

{
"message": "Subscription created successfully",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"subscriptionId": "sub_abc123"
}

Request Fields​

FieldTypeRequiredDescription
customerIdstringYesPayConnect customer ID the subscription bills. The customer must already exist — create one via the Customers API first.
planIdstringYesSubscription plan ID
startDatestringYesBilling start date (ISO 8601)
billingCyclestringNoIgnored — the subscription inherits the plan's single billing cycle. Accepted for backward compatibility; any value sent is discarded.
billingModestringNorecurring (auto-charge each cycle), recurringManual (renews and invoices each cycle but the customer pays manually — never auto-charged), or nonRecurring (one-time). Defaults to recurring. See Billing modes.
trialDaysnumberNoNumber of trial days before first charge. Whole number, 0–365.
amountnumberNoOverride the plan's recurring price for this subscription. When supplied, this is the recurring total — add-ons, discounts, and tax do not apply on top. Mutually exclusive with addOns/discounts. Must be greater than 0 and at most 99999.99.
addOnsarrayNoAdditional products billed each cycle on top of the plan price. Cannot be combined with amount. See Add-ons.
discountsarrayNoApplied discounts. Cannot be combined with amount.
paymentDueDaysnumberNoGrace period, in days, before a generated invoice is due (default 30). See Payment due date.
suspendAfterDaysnumberNorecurringManual only. Days after the invoice due date before an unpaid subscription moves from past_due to suspended (default 30). Ignored for other billing modes. See Manual renewal.
customFieldsobjectNoClient-defined custom field values, keyed by field key

Payment rails on subscription invoices​

A subscription's paymentMethods governs the rails its hosted payment-setup page offers. It does not decide which rails the invoices it generates accept — that is resolved per invoice, and the customer's defaultPaymentOptions takes precedence over the subscription's value:

  1. The billed customer's defaultPaymentOptions, if set.
  2. Otherwise the subscription's paymentMethods.
  3. Otherwise both rails.

This applies to the initial invoice and to every renewal invoice.

The setup page can offer more rails than the invoices accept

Because the customer's setting wins for invoices while the subscription's own value still drives its setup page, a subscription whose paymentMethods lists both rails, for a customer set to ACH only, shows card and bank options on the setup page but produces invoices that accept ACH only. That is expected.

subscription.paymentMethods is read-only over the API, so there is currently no way to narrow the setup page to match. No subscription create or update request accepts the field — sending it is silently ignored, like any unknown key — and it defaults to both rails when a record carries no value. It is reported on subscription reads, and the invoice resolver above consults it, so a record that does carry a value still influences both its setup page and its invoices; you just cannot set one from here.

A customer edit never writes subscription.paymentMethods — the subscription record is left exactly as it was.

Payment due date (paymentDueDays)​

paymentDueDays sets the grace period on the invoices a subscription generates: each invoice's due date is placed this many days after the invoice is issued. If you don't send the field, it defaults to 30 days.

It applies whether or not the subscription auto-charges:

  • Auto-charging subscriptions still attempt the charge on the renewal date. If that charge fails, the invoice stays payable until its due date — giving the customer a window to pay it manually (for example, after updating their card) before it's considered overdue.
  • Manual subscriptions (no saved payment method on file) issue the invoice with this due date each cycle for the customer to pay.

Renewal invoices are prepared a few days before the renewal date, so a very small paymentDueDays never makes an invoice due before its renewal/charge date — the due date is floored at the renewal date in that case.

Automatic payment retries (dunning) are independent of paymentDueDays — they run on the schedule configured for your account and are not shortened or extended by this value.

The subscription's own status reflects whether the invoice gets paid, not this field directly: an unpaid renewal parks the subscription as pending (manual, no payment method) or failed (a failed auto-charge); for recurringManual subscriptions it moves to past_due and then suspended (see Manual renewal). Paying the invoice returns any of these to active.

Example: "paymentDueDays": 45 issues invoices that come due 45 days after they are generated.

Custom amount (override the plan price)​

To bill a specific amount instead of the plan's configured price, include amount. The value becomes the recurring total for that subscription — useful when each customer is billed a different figure against a shared plan. The plan still governs the trial, setup fee, and dunning policy; only the recurring price changes. amount cannot be combined with addOns or discounts.

curl -X POST https://dev.payconnect.us/api/subscriptions/v2 \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"customerId": "cust_abc123",
"planId": "plan_xyz789",
"startDate": "2026-04-01",
"amount": 149.99
}'

Add-ons​

Each add-on line is billed every cycle on top of the plan's recurring price. An add-on always references a product — its unit price and display name resolve live from the product at billing time and cascade on product changes. You cannot set or override the price on an add-on line.

FieldTypeRequiredDescription
productIdstringYesThe product this add-on references. Its price and display name resolve live at billing time, so a later change to the product automatically flows onto the subscription at its next renewal.
quantityintegerYesNumber of units (must be a positive integer).
curl -X POST https://dev.payconnect.us/api/subscriptions/v2 \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"customerId": "cust_abc123",
"planId": "plan_xyz789",
"startDate": "2026-04-01",
"addOns": [
{ "productId": "prod_abc123", "quantity": 2 },
{ "productId": "prod_def456", "quantity": 1 }
]
}'

Billing Modes​

ModeDescription
recurringPayConnect automatically charges the customer's stored payment method on each billing cycle
recurringManualRenews and generates an invoice each cycle like recurring, but PayConnect never auto-charges — the invoice is emailed and the customer pays it manually. No stored payment method is required. See Manual renewal.
nonRecurringPayConnect generates a single invoice the customer pays (card/ACH, or recorded as paid). The subscription activates once the invoice is paid, then terminates — no further invoices or charges.

recurringManual is only available on plan-driven subscriptions — the custom subscription endpoint (fixed-term one-shots) accepts only recurring/nonRecurring.

Manual renewal (recurringManual)​

A recurringManual subscription bills on a recurring cadence but is paid by the customer, not auto-charged. Its status is driven by whether the current invoice is paid on time:

StatusMeaning
activeThe current invoice is paid; the subscription is in good standing.
past_dueThe invoice has passed its due date (paymentDueDays) and is unpaid — late but still live.
suspendedThe invoice is additionally past suspendAfterDays (counted from the due date) and still unpaid. A suspended subscription stops renewing until the invoice is paid.

Paying the outstanding invoice (card/ACH on the hosted page, or recorded as paid) returns a past_due or suspended subscription to active and advances the cycle. suspendAfterDays defaults to 30; set it (including 0, to suspend as soon as the invoice is overdue) at create time.

Get a Subscription​

curl -X GET https://dev.payconnect.us/api/subscriptions/v2/sub_abc123 \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID"

Response:

{
"subscriptionId": "sub_abc123",
"status": "active",
"planName": "Professional Plan",
"totalAmount": 99.00,
"billingCycle": "monthly",
"nextBillingDate": "2026-06-01T12:00:00.000Z",
"customerId": "cust_abc123"
}

Search Subscriptions​

Filter and paginate subscriptions:

Results are paginated with an opaque cursor — limit bounds each page, and you follow nextCursor until it is null. Results come back in subscription creation order. See Paginating large result sets below for the full contract.

curl -X POST https://dev.payconnect.us/api/subscriptions/v2/search \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"status": { "value": "active", "operator": "eq" }
},
"limit": 20
}'

Response (201 Created):

{
"subscriptions": [
{
"subscriptionId": "sub_abc123",
"status": "active",
"planName": "Professional Plan",
"totalAmount": 99.00,
"billingCycle": "monthly",
"nextBillingDate": "2026-06-01T12:00:00.000Z"
}
],
"nextCursor": null,
"hasMore": false,
"truncated": false
}

nextCursor is the pagination cursor — a string until the last page, then null (the only completion signal). hasMore mirrors nextCursor != null and is the loop condition. truncated is true only when a customer free-text filter's directory lookup was itself too large to read in full — the subscription set is always paged to completion, so paging can never be the source of truncation. A Warning response header is also set when truncated is true.

Available Filters​

FilterOperatorsDescription
statuseq, inSubscription status
billingCycleeq, inBilling frequency
billingModeeqrecurring, recurringManual, or nonRecurring
totalAmounteq, gt, gte, lt, lte, betweenAmount in dollars
nextBillingDateeq, gt, gte, lt, lte, betweenNext charge date
createdAteq, gt, gte, lt, lte, betweenCreation date
updatedAteq, gt, gte, lt, lte, betweenLast-modified date — see below
emaileq, contains, startsWithCustomer email
customerIdeq, inCustomer ID
planIdeqPlan ID

Filtering by updatedAt​

Use updatedAt to pull only the subscriptions that changed since you last looked, instead of re-reading the full list.

curl -X POST https://dev.payconnect.us/api/subscriptions/v2/search \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filters": {
"updatedAt": { "value": [1757462400000, 1757548800000], "operator": "between" }
},
"limit": 100
}'

Timestamps compare exactly; date-only strings cover a whole day. A number (epoch milliseconds) or a full datetime string such as 2026-09-10T14:30:00.000Z is matched to the millisecond. A date-only string such as 2026-09-10 covers that entire UTC day — with gte/lt anchored to the start of the day and gt/lte to its end, so the day you name is always inside the range you asked for. A datetime with no offset is read as UTC, so include one (Z or +02:00) if you mean another zone. A value that cannot be parsed as a date returns a 400 rather than quietly returning everything.

Give between its bounds in order. [start, end] with start no later than end; a reversed range returns a 400 rather than an empty page, so a swapped range shows up as an error instead of looking like "nothing changed".

What counts as an update. Any write bumps updatedAt, including mechanical ones — an email event, a status sweep, a renewal charge. Delivery is at-least-once: a retried payment postback legitimately re-bumps the timestamp and the subscription appears again, so dedupe by subscriptionId.

This is a bounded read, not a change feed. Poll a closed interval — between with both bounds in the past — rather than an open gte against "now": a subscription modified while the response is being assembled would otherwise be missed by the next poll. Read every match by paging the cursor until nextCursor is null (see "Paginating large result sets" below) — that is the only completion signal for the subscription result set; a single limit-sized response is not the full set. Note truncated does not cover this — it reflects only a bounded customer-directory lookup behind a customer filter, not the subscription page, so never treat truncated: false as "I have every subscription." Follow nextCursor to null instead.

Paginating large result sets (limit / cursor)​

Search is cursor-paginated. Each request returns up to limit matches (1–100, default 20) plus an opaque nextCursor; you read the complete result set by following nextCursor until it comes back null. There is no page number and no total count — a keyset cursor reads only what it returns, which is what lets it stay fast and complete on an account of any size, without the multi-thousand-row cap that a materialize-and-slice page-number scan hits. (The older page / pageSize inputs were removed; sending either returns a 400. sortBy / sortOrder are accepted only on a customer-scoped search. See Sorting a customer's subscriptions.)

Under a sustained burst of search traffic the endpoint may return 429 with a Retry-After header — back off for that many seconds and retry the same request (same cursor); it is a transient capacity signal, not a bad request.

# First page: omit `cursor` (an empty string "" also works).
curl -X POST https://dev.payconnect.us/api/subscriptions/v2/search \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"filters": { "updatedAt": { "value": [1757462400000, 1757548800000], "operator": "between" } },
"limit": 100
}'

Response — the flat cursor envelope:

{
"subscriptions": [ /* up to `limit` matches, in creation order */ ],
"nextCursor": "<opaque-cursor-from-this-response>",
"hasMore": true,
"truncated": false
}

The nextCursor value is an opaque, versioned token — do not parse, construct, or persist it; treat it as a string and pass it back verbatim as cursor to fetch the next page. Repeat while hasMore is true (equivalently, until nextCursor is null).

Things to know about cursor pagination:

  • nextCursor: null is the ONLY completion signal. A page may contain fewer than limit items — even zero — while nextCursor is still non-null (a sparse filter can scan a full internal budget without filling a page). Never infer "done" from an empty or short page; loop on hasMore / page until nextCursor is null. A client that stops on an empty array silently misses rows.
  • No total count. A keyset cursor cannot cheaply report a grand total, so there is no totalItems / totalPages — completeness is driven by nextCursor, never by a page count.
  • Results come back in creation order. A company-wide search's order is fixed (append order). For a change-driven sync this is ideal — you page the whole set and order it yourself. A search scoped to customers can instead be sorted server-side (below).
  • Changing limit between pages is safe. The cursor encodes position, not an offset, so you can grow or shrink limit mid-traversal without skipping or repeating rows.
  • The customer free-text filter is supported. It resolves to the matching customers and pages their subscriptions in full. If the customer directory itself is too large to read completely, the response sets truncated: true (with a customerSearch block giving the details) — the one case where some matching subscriptions may be missing.
  • Custom-field filters are not index-accelerated in cursor mode. Page-number search can serve an indexed custom-field filter from a secondary index; cursor mode deliberately does not, because that index is itself capped and so cannot back cursor mode's complete-results guarantee. A custom-field-filtered cursor read therefore scans the subscription partition in pages rather than doing a targeted index lookup — a deliberate trade of index speed for completeness. For a large tenant filtering on an indexed custom field where a truncated, index-fast result is acceptable, page-number search remains the faster option.

Sorting a customer's subscriptions (sortBy / sortOrder)​

A search scoped to customers can be sorted server-side. It counts as scoped when it has a customerId filter with eq, or in with up to 100 ids, or a customer filter that matches at most 100 customers:

{
"filters": { "customerId": { "value": "cust_abc123", "operator": "eq" } },
"sortBy": "nextBillingDate",
"sortOrder": "asc",
"limit": 25
}
  • sortBy is one of nextBillingDate, createdAt, status, or totalAmount. Plan name is not sortable.
  • sortOrder is asc (the default) or desc. Sending it without sortBy returns a 400.
  • Ties are broken by subscription id, and subscriptions missing the sort field come last in either direction, so the order is fully deterministic. status sorts alphabetically by its value (active, cancelled, ended, …).
  • Page with nextCursor exactly as above. The cursor carries the sort, so passing it back with a different sortBy or sortOrder (or without sortBy) returns a 400. Keep the sort fixed for a whole traversal. As with every cursor search, its last page has hasMore: false even when it is exactly full (no trailing empty page).
  • Sort on a value that can change mid-traversal at your own risk. Each page re-reads the live set and resumes after the last row you received. If a subscription's sort value changes between your requests (a renewal moves its nextBillingDate, dunning changes its status, an update changes its totalAmount), it's placed by its new value: it can show up twice or be skipped. Only createdAt gives a complete read, because it never changes. On the other sorts, deduping by subscriptionId removes a repeat but cannot recover a skipped row; page quickly to shrink the window, and re-read with createdAt when you need every row.
  • Sorting requires the customer scope. sortBy on a company-wide search (no customer filter, or more than 100 customers) returns a 400. Company-wide results always come back in creation order, because sorting them would mean reading the entire account on every page.
  • A sorted search reads the scoped customers' whole subscription set on every page, so a scope holding more than 10,000 subscriptions (counted before your other filters, since they don't reduce the read) returns a 400 asking you to narrow the customer set, or to omit sortBy.
  • While sorting is rolling out, an environment where it is not yet enabled returns a 400 saying sorting is not available yet, before reading anything. Omit sortBy and sort the results yourself; the same search without sortBy always works.

Worked example: delta-sync​

To keep a local mirror current, combine the updatedAt filter (what changed) with cursor pagination (read all of it):

  1. Pick a closed interval [lastSyncedAt, now] (both in the past).
  2. Search with that updatedAt between filter and cursor: "".
  3. Upsert each returned subscription into your store, keyed by subscriptionId.
  4. If nextCursor is non-null, search again with the same filter and cursor: nextCursor; repeat until it is null.
  5. Advance lastSyncedAt to now for the next run.

Because delivery is at-least-once (a retried postback re-bumps updatedAt), the same subscription can appear more than once across runs — dedupe by subscriptionId on upsert. Paging to nextCursor: null guarantees you saw every subscription that changed in the interval, with no silent truncation.

Summarize a Customer's Subscriptions​

POST /api/subscriptions/v2/summary returns subscription counts and recurring value for one customer or a small group of customers, in one request. Use it for dashboards and badges ("3 subscriptions · 2 active") instead of paging a whole list just to count it. Search responses deliberately carry no totals; this is where they come from.

The request takes the same typed filters as Search Subscriptions, and must be scoped to customers: customerId with eq (one id) or in (up to 100 ids), or a customer filter matching at most 100 customers. Any other filters narrow the summary exactly as they narrow a search:

{
"filters": {
"customerId": { "value": ["cust_abc123", "cust_def456"], "operator": "in" },
"billingCycle": { "value": "monthly", "operator": "eq" }
}
}

Response (HTTP 201):

{
"byStatus": [
{ "status": "active", "count": 2, "totalAmount": 59.98 },
{ "status": "cancelled", "count": 1, "totalAmount": 19.99 }
],
"total": { "count": 3, "totalAmount": 79.97 }
}
FieldDescription
byStatusOne bucket per subscription status that has at least one matching subscription, in a fixed status order. Empty when nothing matches.
byStatus[].countNumber of matching subscriptions in that status.
byStatus[].totalAmountSum of the subscriptions' totalAmount (the per-cycle charge).
total.count, total.totalAmountTotals across every status.

Amounts are in the same units as a subscription's totalAmount, rounded to cents. Subscriptions on different billing cycles are summed as stored, not converted to a common period, so add a billingCycle filter when you need a monthly or annual figure. To count only live subscriptions, filter by status or read the matching bucket.

  • A request without a customer scope, or with more than 100 customers, returns a 400. The endpoint never aggregates a whole account.
  • A customer filter whose customer lookup is too large to read completely returns a 400 (the counts would otherwise be silently low): filter by customerId instead.
  • The summary reads every subscription of the customers in scope, so a scope holding more than 10,000 subscriptions (counted before your other filters) returns a 400 asking you to narrow the customer set.
  • While this feature is rolling out, an environment where it is not yet enabled returns a 400 saying summaries are not available yet, before reading anything. Count by paging Search Subscriptions instead.
  • Under a sustained burst of traffic it may return 429 with a Retry-After header: back off for that many seconds and retry the same request.

Manage a Subscription​

Use the PUT endpoint with an operation field to manage subscription state.

Cancel​

curl -X PUT https://dev.payconnect.us/api/subscriptions/v2/sub_abc123 \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"operation": "cancel",
"cancelAtPeriodEnd": true,
"reason": "Customer requested cancellation"
}'

Set cancelAtPeriodEnd: true to let the subscription run until the current billing period ends. Set it to false for immediate cancellation.

Update​

curl -X PUT https://dev.payconnect.us/api/subscriptions/v2/sub_abc123 \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"operation": "update",
"updates": {
"amount": 149.00,
"billingCycle": "yearly"
}
}'

Record a Manual Payment​

Manual/offline payments (check, cash, money order) are recorded against an invoice from the PayConnect dashboard — there is no public API endpoint for recording a manual payment on a subscription. Open the invoice in the dashboard and use Record Manual Payment there; the recorded payment is reflected on the subscription's linked invoices.

Pay a Subscription​

Charge a stored payment method for a subscription:

curl -X POST https://dev.payconnect.us/api/subscriptions/v2/sub_abc123/pay \
-H "x-session-token: YOUR_SESSION_TOKEN" \
-H "x-correlation-id: YOUR_CORRELATION_ID" \
-H "Content-Type: application/json" \
-d '{
"accountVaultId": "av_xyz789",
"amount": 99.00,
"savePaymentMethod": true
}'

Response:

{
"message": "Payment processed successfully",
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"subscriptionId": "sub_abc123",
"transactionId": "trx_def456",
"status": "success"
}

Subscription Statuses​

StatusDescription
pendingSubscription created, awaiting first payment or start date
activeSubscription is active and billing
cancelledSubscription cancelled
failedPayment failed (enters dunning)
endedSubscription completed its term

Dunning (Failed Payment Retry)​

When a recurring payment fails, PayConnect automatically retries according to your dunning schedule:

  1. Initial failure — subscription status changes to failed
  2. Retry attempts — PayConnect retries the charge on a configured schedule
  3. Email notifications — customers receive payment failure and retry notifications
  4. Final failure — after all retries are exhausted, the subscription may be suspended
info

Dunning schedules are configured per company. Contact [email protected] to customize retry intervals and maximum attempts.

Customer Self-Service​

PayConnect provides a hosted subscription management page where your customers can:

  • View subscription details and billing history
  • Update their payment method
  • Add or remove stored payment methods

The management page uses a secure authentication flow:

  1. Customer visits the management URL with their subscription token
  2. PayConnect sends a verification code via email or SMS
  3. Customer enters the code to authenticate
  4. Customer can view and manage their subscription

Next Steps​

  • Customers — manage customer records and payment methods
  • Invoices — send one-time payment requests
  • Postbacks — receive subscription event notifications
  • API Reference — full endpoint documentation