Prerequisites
- A Bouclay account with a payment gateway connected (Developers → Payment gateways) in test mode.
- A secret key from Developers → API Keys. Test keys start with
sk_test_. - A webhook endpoint, registered under Developers → Webhooks or with
POST /webhook-endpoints. Keep its signing secret (whsec_…); it’s shown once.
Authorization: Bearer sk_test_…. Every POST and PATCH also sends an Idempotency-Key: reuse the same key when you retry the same request, and a new one for each new request. Amounts are in major units (5000 is ₦5,000).
Build it
1
Create the customer when someone signs up
Use a key derived from your own user id so a double-submitted signup can’t create two customers.Store the returned
id (ctm_…) against your user.2
Create the plan, once
A recurring price with a trial. The inline Keep the price id from
price accepts every field POST /products/{id}/prices does.priceIds. In the dashboard, open Catalog → Entitlements, create the capabilities Pro unlocks (for example pdf_export), and grant them to the product.3
Start the trial
trialing and nothing is charged.4
Save a card before the trial ends
A checkout session saves a card through a small verification charge that is refunded automatically. Pass Redirect the customer to
returnUrl so the customer comes back to your app afterwards.checkoutUrl. They return to https://yourapp.com/billing?checkoutSession=…&status=complete (or status=failed). Confirm with GET /checkout-sessions/{id} rather than trusting the query string.The card becomes the customer’s default, and it is what the trial converts onto: a subscription with no card of its own charges the customer’s default card.5
Gate features on entitlements
Check access on your server, not in the browser:
data.granted is true while the customer holds a subscription that grants it. Trialing and active subscriptions grant access; a paused, suspended or canceled one does not.6
Keep in sync with webhooks
Bouclay posts events to your endpoint, signed with
Bouclay-Signature: t=<unix>,v1=<hex>. Verify it as HMAC-SHA256 of "<t>.<raw body>" with your signing secret, reject timestamps older than five minutes, and ignore event ids you’ve already handled. A delivery that was retried can arrive after a newer one, so compare the event’s created time with the last event you applied to that object and skip older ones.data.object is the object exactly as the REST API returns it. Every object has a *.created and a *.updated event, and *.updated fires on any change, so read the state (status, currentPeriodEnd, scheduledChanges) off the object rather than the event name. subscription.* events also carry data.object.customer.entitlements, so you can update your paywall cache without calling back.Going further
-
For a “Manage billing” button, create a portal session and redirect to its
url. It opens the customer’s portal (plan, cards, invoices) for 60 minutes and sends them back to yourreturnUrl: -
Invoices carry
hostedUrl, the page where the customer sees and pays them, andinvoicePdfUrl. List one subscription’s invoices withGET /invoices?subscriptionId=sub_…. -
To refund, take the invoice’s
payment.idandPOST /payments/{id}/refundswith an optionalamount(omit it to refund everything left) andreason. The refund goes back through the gateway that took the payment; a partial refund leaves the paymentsucceeded, andamountRefundedshows the running total. -
Cancel at the end of the period with
POST /subscriptions/{id}/canceland{"mode": "period_end"}; undo it withPOST /subscriptions/{id}/undo-cancel. - Every endpoint and field is in the API reference.