Managed billing and trials
Managed billing runs a recurring subscription at the payment provider with an exact price, a quantity of seats and a monthly or yearly interval. Tinker Payments keeps the subscription, its invoices and its payments in your merchant records. It currently runs on Stripe and requires an authenticated app with a configured Stripe gateway.
Start a checkout
Prices are integer minor units (1500 is €15.00) in EUR. Send a unique idempotency key per checkout and reuse it, with the same body, only to retry a lost response. Add trial_days or trial_end to start a free trial.
Authorization: Bearer <token>
Content-Type: application/json
{
"external_customer_id": "ws_123",
"email": "[email protected]",
"name": "Team seats",
"currency": "EUR",
"unit_amount": 1500,
"quantity": 3,
"interval": "month",
"return_url": "https://app.example.com/billing",
"idempotency_key": "checkout-ws_123-7f3c9a1e",
"trial_days": 14
}Send the customer to the checkout URL in the response. Read the subscription later with GET /merchant/billing/resources/:id using the checkout or subscription ID.
Expire an abandoned checkout
A checkout link stays usable for 24 hours. If a customer abandons it and you start a new checkout, close the old one first so it cannot be completed and create a second subscription.
Authorization: Bearer <token>
Content-Type: application/json
{
"idempotency_key": "expire-cs_123-7f3c9a1e"
}| Checkout status | Meaning |
|---|---|
open | Expired. The checkout is returned with status expired and managed_state.status canceled. |
expired | Returned unchanged, so retries are safe. |
complete | 409 CHECKOUT_COMPLETED with outcome not_applied. Nothing changed; read the checkout and use its subscription. |
The customer can finish paying while your request is in flight; that also returns 409 CHECKOUT_COMPLETED. In that case read the checkout and use its subscription instead of starting a new one. An expired checkout sends no subscription.cancelled webhook.
Free trials with a required card
- trial_days (1 to 730) starts a trial of that many days. trial_end (Unix seconds, 48 hours to 730 days ahead) ends it at an exact time instead, for example for a complimentary period. Send one or the other, or neither for a normal checkout.
- The customer enters a payment method at checkout. Nothing is charged that day. The first full charge happens when the trial ends.
- If the trial ends without a usable payment method, the subscription is cancelled and nothing is charged.
- Tinker Payments does not check who may have a trial. Limiting each customer to one trial is up to your app.
- Older Tinker Payments servers reject unknown fields, so only send trial_days or trial_end when set. The official SDKs do this for you.
| Response | Meaning |
|---|---|
400 INVALID_BILLING_REQUEST | trial_days is outside 0 to 730, trial_end is outside 48 hours to 730 days ahead, both are set, or another field is invalid. |
422 TRIAL_UNSUPPORTED | The app’s payment provider cannot run a trial with a required payment method. Nothing was created (outcome: not_applied). Retry without a trial. |
409 IDEMPOTENCY_CONFLICT | The idempotency key was already used with a different body, for example a different trial_days. |
Read the billing state
Checkout and billing resource responses include managed_state. It is the same for every payment provider, so read it instead of the provider’s own fields, which are kept in the response only for compatibility.
"managed_state": {
"status": "trialing",
"trial_ends_at": "2026-11-05T10:00:00Z",
"current_period_end": "2026-11-05T10:00:00Z",
"cancel_at_period_end": false
}| Status | Meaning |
|---|---|
pending | Checkout not completed. |
trialing | Trial running with a payment method on file. Nothing has been charged yet. |
active | Paid subscription. |
past_due | A charge failed, including the first charge after a trial. |
paused | Collection paused by the provider. |
canceled | The subscription has ended. |
Give access while status is trialing or active and current_period_end is in the future. During a trial, current_period_end equals trial_ends_at. trial_ends_at stays set after the trial ends. Your dashboard shows a trial as an active subscription.
Webhooks during a trial
Managed subscription events include billing_status (the same values as managed_state.status) and trial_ends_at. See Webhooks for signature verification.
| Event | Meaning |
|---|---|
subscription.created | Checkout completed. For a trial, billing_status is trialing and trial_ends_at is set. |
invoice.paid (amount 0) | The trial’s €0 invoice. It is not a payment; do not count it as revenue. |
subscription.trial_will_end | Sent once, up to 3 days before the trial ends. Use it to remind the customer. |
invoice.paid + subscription.renewed | The first real charge at the end of the trial succeeded. |
subscription.cancelled | The trial was cancelled, or it ended without a payment method. |
If the first charge after a trial fails, payment.updated is sent with status failed, but no subscription lifecycle event. The subscription becomes past_due, so read the resource after trial_ends_at to detect it.
Changes during a trial
| Request | Meaning |
|---|---|
POST /resources/:id/cancel | cancel: true ends the subscription when the trial ends, with no charge. cancel: false undoes it. |
POST /resources/:id/quantity | Adds seats immediately. Nothing is charged until the trial ends. Tinker Payments does not cap seats during a trial. |
POST /resources/:id/schedule | Lowers seats from the first paid period. The trial is kept. |
POST /resources/:id/payment-method | Lets the customer change their payment method, as for paid subscriptions. |
Tax is calculated at the first real charge. The trial invoice is €0.
Change the trial end
Use these to extend or shorten a complimentary period, or to revoke it. Both return the subscription with managed_state, like the other resource requests.
| Request | Meaning |
|---|---|
POST /resources/:id/trial-end | Body: trial_end (Unix seconds, 1 hour to 730 days ahead, and at most 730 days after the billing cycle anchor) and idempotency_key. A trialing subscription moves its trial end, earlier or later. A paid (active) subscription can only move its next charge to current_period_end or later; it becomes trialing until then and nothing is credited or charged. |
POST /resources/:id/end-trial | Body: idempotency_key. Only for trialing subscriptions that are not set to cancel; undo a cancellation first if the customer should be charged. Ends the trial and charges the full period now. If the charge fails, the subscription becomes past_due. |
- Both are rejected while a seat reduction is scheduled or a seat change is pending. Clear the schedule first.
- Ending a trial early sends invoice.paid and payment.updated, but not subscription.renewed. Extending a paid subscription sends invoice.paid with amount 0.
- If a trial was added to a paid subscription whose paid period has not ended, ending the trial charges a full new period on top of it. To revoke such an extension, move the trial end back to the paid-through date instead.
- Rejections that depend on the subscription’s state return 502 BILLING_GATEWAY_ERROR with outcome not_applied. An invalid trial_end returns 400 INVALID_BILLING_REQUEST.