Subscriptions
Subscriptions bill a customer the same amount every period. You describe what you sell as products and plans, subscribe a customer to a plan, and PaySigna opens an invoice each period, collects it, retries it when it fails, and tells the customer at each step.
| Object | What it is |
|---|---|
| Product | Something you sell on a subscription — LoveSync Premium. |
| Plan | A price for a product: an amount every so many days, weeks, months or years, with an optional free trial. A product can have several plans — monthly, annual. |
| Subscription | One customer on one plan. |
| Invoice | One period’s bill for a subscription, and every attempt to collect it. |
| Coupon | A percentage or an amount off a subscription’s bills, for one bill, several or all of them. |
Invoices can also be issued by hand, outside any subscription, and reduced by credit notes or customer credit — see Invoices and credit.
Everything here is under the Billing tag in the API tester.
Renewal is a prompt, not a silent debit
Section titled “Renewal is a prompt, not a silent debit”Read this before you write any copy for your customers.
On mobile money, every renewal is a prompt. A wallet cannot be debited without its owner: each period we open an invoice and send a payment prompt to the subscription’s renewal number, and the customer approves it on their phone. If they ignore it, nothing is collected. Do not describe a mobile-money subscription as automatic or auto-renewing — it is not, and a customer who believed it was will lose access without understanding why.
On a card, renewal can be charged without the customer. When a customer pays a bill with a card and the card network allows it to be charged again, the card is stored against the subscription (shown only as, for example, Visa •••• 4242). Each renewal tries that card first, with no prompt. If the card fails on a bill, the retries for that bill are prompts to the mobile-money number on file.
Either way we tell the customer by SMS and email before and during each bill, so a renewal is never a surprise — see What the customer receives.
1. Create a product and a plan
Section titled “1. Create a product and a plan”These are setup, done once — from your server, or from Billing → Plans in the portal.
curl https://sandbox.paysigna.com/v1/products \ -H "Authorization: Bearer psg_test_…" \ -H "Content-Type: application/json" \ -d '{ "name": "LoveSync Premium", "description": "Unlimited matches" }'curl https://sandbox.paysigna.com/v1/plans \ -H "Authorization: Bearer psg_test_…" \ -H "Content-Type: application/json" \ -d '{ "product": "<product id>", "nickname": "Monthly", "amount": 5000, "currency": "GHS", "interval": "month", "interval_count": 1, "trial_days": 7 }'| Field | Notes |
|---|---|
amount |
Per period, in minor units. 5000 is GHS 50.00. |
interval |
day, week, month or year. |
interval_count |
Bill every this many intervals. 3 with month is quarterly. Defaults to 1. |
trial_days |
Optional free trial. Nothing is collected during it. |
Every field is in the reference: create a product, create a plan.
Showing plans on a pricing page
Section titled “Showing plans on a pricing page”GET /v1/plans also accepts a publishable key holding plan.read, and then
returns active plans only — safe to call from a browser to render a pricing page.
With a secret key it returns every plan, with its subscribers count.
2. Subscribe a customer
Section titled “2. Subscribe a customer”POST /v1/subscriptions, with a secret key holding billing.write. The
Idempotency-Key header is required: a retried create must not put the
customer on the plan twice.
The customer is a stored customer’s id, or a phone, email and name — in
which case they are stored for you, exactly as a payer is on a charge.
There are three ways to start, depending on whether the plan has a trial and how you want the first bill paid. All fields: create a subscription.
Prompt the first bill to their phone
Section titled “Prompt the first bill to their phone”curl https://sandbox.paysigna.com/v1/subscriptions \ -H "Authorization: Bearer psg_test_…" \ -H "Idempotency-Key: 9f3c2a71-4b8e-4d1f-a6c5-0e2b7d9f8a14" \ -H "Content-Type: application/json" \ -d '{ "plan": "<plan id>", "customer": { "phone": "0541234567", "name": "Ama Mensah", "email": "[email protected]" }, "payment_method": { "phone": "0541234567", "momo_network": "mtn" } }'The subscription comes back incomplete with its first invoice open, and a prompt
goes to that number within a minute. It becomes active when the invoice is paid.
payment_method.phone is the renewal number every later bill is prompted on;
it defaults to the customer’s phone. The network is named by code or by
momo_network_id, as on a charge.
Let them pay the first bill on your checkout page
Section titled “Let them pay the first bill on your checkout page”Send checkout instead of payment_method:
{ "plan": "<plan id>", "checkout": { "return_url": "https://your-app.example.com/subscribed" }}The response carries a payment_session with an id, a client_token and
expires_at. Hand the client_token to your checkout page exactly as for any
payment session — it is shown once. Whatever the customer pays from becomes how
the subscription renews: the mobile-money number they approve on, or the card.
payment_method on the subscription is absent until that first bill is paid.
The session asks for the first bill’s actual amount, after any coupon and
credit, not the plan price. If a coupon or credit covers the whole first bill,
there is no payment_session in the response: the subscription is already
active.
Start a free trial
Section titled “Start a free trial”On a plan with trial_days, the subscription starts trialing, nothing is
collected, and the first invoice opens when the trial ends. payment_method is
required, because there is no first payment to learn the number from, and
checkout is refused.
With a coupon
Section titled “With a coupon”Add "coupon" with a coupon’s id, or the code the customer typed (case does not
matter). The discount comes off from the first bill — or, on a trial, from the
first bill after it. A code that is unknown, inactive, past its redeem_by or
fully redeemed is refused with invalid_field on coupon, and the subscription
is not created. See Coupons and discounts.
If the coupon, or the customer’s credit,
covers the whole first bill, nothing is collected: the subscription comes back
active and invoice.paid fires with amount: 0.
Statuses
Section titled “Statuses”| Status | Meaning |
|---|---|
incomplete |
Created; the first bill is open and not yet paid. |
trialing |
In a free trial. Nothing collected yet. |
active |
Paid up for the current period. |
past_due |
An attempt to collect the current bill failed. Retries continue. |
paused |
Billing stopped. No invoice opens while paused. |
canceled |
Ended. cancel_reason says why: merchant, customer or unpaid. |
current_period_start and current_period_end are the period that has been paid
for — or is being billed. cancel_at_period_end is true when it will end at
current_period_end rather than renew.
Invoices and the retry schedule
Section titled “Invoices and the retry schedule”Each period opens one invoice — never two for the same period, and never more than
one open at a time. GET /v1/invoices lists them (filter by subscription or
status); GET /v1/invoices/{id} adds its lines, every attempt to collect it and the
charge each one created.
A period’s bill is the plan’s price, less the subscription’s discount, less any
credit the customer holds: amount = subtotal − discount − credit_applied. The
fields are described in
What an invoice is made of.
When a bill is not paid, it is attempted again on a schedule counted from the day it opened:
| Default | |
|---|---|
| Attempts on days | 0, 1, 3, 5 |
| If still unpaid on day | 7 — the invoice becomes uncollectible and the subscription ends with cancel_reason: unpaid |
| Renewal notice | 3 days before each renewal on monthly and longer plans, and before a trial ends |
You can change all three in the portal under Billing → Retries. Retry days
apply at once, including to the next retry of a bill already open (still counted
from the day it opened). The end day is fixed when a bill opens — it is the
invoice’s collect_until, the pay-by date the customer was told — so a change
applies only to bills opened afterwards. Notice days apply from the next notice.
next_attempt_at is the next attempt.
Every attempt is an ordinary charge. It appears in your transactions with the
invoice number as its reference, settles into your balance like any payment, and
can be refunded like any payment. invoice.transaction is the charge that paid it.
Coupons and discounts
Section titled “Coupons and discounts”A coupon takes a percentage or a fixed amount off a subscription’s bills.
POST /v1/coupons:
{ "name": "Launch offer — 20% off for 3 months", "code": "LAUNCH20", "percent_off": 20, "duration": "repeating", "duration_periods": 3, "max_redemptions": 500, "redeem_by": "2026-12-31T23:59:59Z"}| Field | Notes |
|---|---|
name |
Required. Shown on the invoice’s discount line. |
code |
Optional. What a customer types: 3 to 40 letters, digits, dashes or underscores. Stored in capitals; case does not matter when it is used. Leave it out for a coupon only you apply, by id. |
percent_off |
Above 0, at most 100. |
amount_off + currency |
A fixed amount in minor units. Send this or percent_off, not both. |
duration |
once (the next bill), repeating (duration_periods bills, 1 to 60) or forever. |
max_redemptions |
Optional. How many subscriptions may use it. |
redeem_by |
Optional. The last moment it can be applied. Must be in the future. |
products |
Optional. Product ids it is restricted to. Empty means any. |
All fields: create a coupon.
A coupon cannot be edited, only switched off: POST /v1/coupons/{id} with
{ "active": false }. An inactive coupon cannot be applied; subscriptions that
already carry it keep it.
Applying one
Section titled “Applying one”At creation with coupon (above), or later:
| Call | What it does |
|---|---|
POST /v1/subscriptions/{id}/discount with { "coupon": "<id or code>" } |
Applies the coupon, replacing any the subscription had. Discounts do not stack. |
DELETE /v1/subscriptions/{id}/discount |
Removes the discount. |
Both apply from the next bill opened. A bill already open keeps its amount, because the customer may be approving it right now. A subscription that has ended cannot take a coupon.
Each application counts one redemption, towards max_redemptions and in
times_redeemed. A coupon is refused if it is restricted to other products, or if
it is a fixed amount in a currency other than the plan’s.
What it takes off
Section titled “What it takes off”A percentage is rounded down to the minor unit. A fixed amount never takes off
more than the bill. The discount is a negative discount line on the invoice,
named after the coupon, and the invoice’s discount field. A once or
repeating coupon counts down one bill each time it takes something off. When it
reaches zero the subscription is billed at the full price again.
Cancelling, pausing, resuming
Section titled “Cancelling, pausing, resuming”| Call | What it does |
|---|---|
POST /v1/subscriptions/{id}/cancel |
Ends it now, voiding any open invoice. |
… with { "at_period_end": true } |
Keeps it until the paid period runs out, then ends it. Nothing more renews. |
POST /v1/subscriptions/{id}/pause |
Stops billing. No invoice opens while paused. |
POST /v1/subscriptions/{id}/resume |
Restarts billing. If the paid period ran out while it was paused, the next one starts now and is billed at once. |
The customer is emailed when a subscription ends. Customers can also cancel themselves — see below.
What the customer receives
Section titled “What the customer receives”PaySigna sends these to your customer, on your behalf and with your name on them:
| When | How | |
|---|---|---|
| Subscribed | A welcome, with their manage code. | |
| Before each renewal, and before a trial ends | SMS and email | The amount and the date. |
| A bill is due | SMS and email | Sent with the first prompt. Not sent when a stored card is being charged, since nothing is asked of them. |
| A payment failed | SMS and email | With the date they must pay by. |
| A bill was paid | A receipt. | |
| The subscription ended | — |
Every notice carries the customer’s manage code. On the PaySigna subscription page they type that code to see their subscription, Pay now a missed bill (which sends a fresh prompt), or Cancel. Cancelling there keeps what they paid for until the end of the period; if nothing has been paid yet it ends at once. The code is typed on the page; it is never put in a link. See Billing.
Webhooks
Section titled “Webhooks”| Event | When |
|---|---|
subscription.created |
A subscription was created. |
subscription.activated |
The first invoice was paid, or a trial began. |
subscription.trial_will_end |
A trial ends soon; the first bill follows. |
subscription.renewal_upcoming |
A renewal notice was sent to the customer. |
subscription.past_due |
Collecting an invoice failed; retries continue. |
subscription.cancel_scheduled |
It will end when the current period does. |
subscription.canceled |
It ended. |
subscription.paused / subscription.resumed |
Billing paused or resumed. |
invoice.created |
An invoice opened for a period. |
invoice.paid |
An invoice was paid. Carries the paying transaction_id. |
invoice.payment_failed |
An attempt failed. More may follow. |
invoice.uncollectible |
The retries ran out. The subscription ends. |
invoice.paid can arrive with amount: 0 and no transaction_id when a coupon
or credit covered the whole bill. Treat it like any other payment: the period is
paid. One-off invoices and credit notes have events of their own — see
Invoices and credit.
Grant and revoke access on invoice.paid and subscription.canceled. Those
are the two that change what the customer is entitled to. past_due is a warning,
not an ending — the customer can still pay within the retry window. Each charge
attempt also fires the ordinary transaction.* events; build entitlement on the
invoice events rather than on those, because a transaction does not say which
period it paid for.
As with every webhook, verify the signature, deduplicate on event id, and read
the subscription or invoice when you need its current state. See
Webhooks.
Scopes
Section titled “Scopes”| Scope | Key | Grants |
|---|---|---|
billing.read |
Secret | Read products, plans, subscriptions, invoices, coupons, credit notes and customer credit. |
billing.write |
Secret | Create products, plans and subscriptions; cancel, pause and resume; coupons and discounts; one-off invoices, voids, credit notes and credit grants. Needs verification level 1 on a live key. |
refund.create |
Secret | Also needed for a credit note with outcome: refund. |
plan.read |
Publishable or secret | List active plans, for a pricing page. Safe in a browser. |
In the sandbox
Section titled “In the sandbox”Subscriptions behave exactly as in live, including the retry schedule and the
notices (prefixed [TEST]). A successful sandbox card payment stores a reusable
card, so you can test card renewals without a real one. See
Sandbox and testing.
Not supported yet
Section titled “Not supported yet”Proration, changing plan mid-period, and metered usage. To move a customer to a different price, cancel at period end and subscribe them to the new plan.

