Skip to main content
This walks through the integration a typical SaaS needs: a customer, a monthly plan with a 7-day trial, a saved card that is charged when the trial ends, and a paywall driven by entitlements.

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.
Every request sends 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 price accepts every field POST /products/{id}/prices does.
Keep the price id from 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

The subscription starts 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 returnUrl so the customer comes back to your app afterwards.
Redirect the customer to 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 your returnUrl:
  • Invoices carry hostedUrl, the page where the customer sees and pays them, and invoicePdfUrl. List one subscription’s invoices with GET /invoices?subscriptionId=sub_….
  • To refund, take the invoice’s payment.id and POST /payments/{id}/refunds with an optional amount (omit it to refund everything left) and reason. The refund goes back through the gateway that took the payment; a partial refund leaves the payment succeeded, and amountRefunded shows the running total.
  • Cancel at the end of the period with POST /subscriptions/{id}/cancel and {"mode": "period_end"}; undo it with POST /subscriptions/{id}/undo-cancel.
  • Every endpoint and field is in the API reference.