Skip to content

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.

POST https://api.tinkerpayments.com/v1/merchant/billing/checkout
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.

POST https://api.tinkerpayments.com/v1/merchant/billing/checkout/{checkoutId}/expire
Authorization: Bearer <token>
Content-Type: application/json

{
  "idempotency_key": "expire-cs_123-7f3c9a1e"
}
Expire results
Checkout statusMeaning
openExpired. The checkout is returned with status expired and managed_state.status canceled.
expiredReturned unchanged, so retries are safe.
complete409 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.
Trial checkout errors
ResponseMeaning
400 INVALID_BILLING_REQUESTtrial_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_UNSUPPORTEDThe 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_CONFLICTThe 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 during a trial
"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
}
managed_state.status values
StatusMeaning
pendingCheckout not completed.
trialingTrial running with a payment method on file. Nothing has been charged yet.
activePaid subscription.
past_dueA charge failed, including the first charge after a trial.
pausedCollection paused by the provider.
canceledThe 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.

Trial webhook events
EventMeaning
subscription.createdCheckout 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_endSent once, up to 3 days before the trial ends. Use it to remind the customer.
invoice.paid + subscription.renewedThe first real charge at the end of the trial succeeded.
subscription.cancelledThe 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

Subscription changes during a trial
RequestMeaning
POST /resources/:id/cancelcancel: true ends the subscription when the trial ends, with no charge. cancel: false undoes it.
POST /resources/:id/quantityAdds seats immediately. Nothing is charged until the trial ends. Tinker Payments does not cap seats during a trial.
POST /resources/:id/scheduleLowers seats from the first paid period. The trial is kept.
POST /resources/:id/payment-methodLets 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.

Trial end requests
RequestMeaning
POST /resources/:id/trial-endBody: 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-trialBody: 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.