Payments infrastructure for high risk
Payments for the merchants everyone else declines.
Token Gesture is a payments gateway and billing engine built for adult and creator subscriptions, dating, nutraceuticals and gaming. The developer experience you expect. Acquirers that actually take the file.
Decision in four business days · No setup fee · Keep your existing checkout
// one call, any of five currencies const { checkout_url } = await billing.checkout.create({ organisationId, plan, currency: "AUD", });
Four categories, one file, no surprise offboarding.
Mainstream processors decline on category code before a human reads the file. We underwrite the business — your refund policy, your descriptor, your dispute handling, your moderation. If we approve you, we don't quietly reverse it eight months later when a risk model updates.
Adult & creator subscriptions
Recurring content access, tiered membership, pay-per-message. Age verification and consent records reviewed at onboarding, not after a complaint.
Dating & companionship
Subscriptions, credits and boosts, built for the refund pattern dating actually has rather than the one retail models assume.
Nutraceuticals & supplements
Trials and continuity billing, with negative-option rules enforced in the rebill engine so a trial can't quietly become a dispute.
Gaming & skill wagering
Jurisdiction-scoped acceptance, per-country BIN routing, and licence evidence held on file for the acquirer.
Everything a subscription business needs, plus the parts high risk adds.
The billing surface is ordinary — plans, subscriptions, discounts, statements. What's different sits underneath: acquirer cascading, dispute alerting, and settlement in a currency your treasury can actually bank.
Cascading acquirers
A soft decline retries against the next acquirer on your file inside the same authorisation window. Your customer sees one attempt.
Pre-chargeback alerts
Ethoca and Verifi alerts refund the order before it becomes a chargeback, so the transaction never lands in your ratio.
Rebill engine
Monthly and yearly periods as ISO 8601 durations, trials, scheduled plan changes, and cancel-time retention discounts.
Five currencies, banked
AUD, NZD, USD, EUR and JPY priced natively in minor units. JPY is zero-decimal and stays that way end to end — no silent ×100.
Dunning that isn't spam
Retry schedules tuned per issuer BIN, card-updater lookups before the first retry, and a hard stop before retries become complaints.
Reserve you can see
Rolling reserve balance, release schedule, and every held transaction listed in the dashboard. High risk means a reserve; it doesn't mean a mystery.
Integrate in an afternoon, not a quarter.
One typed npm package wraps the service API. No card data reaches your servers, so your PCI scope stays exactly where it is today.
import { BillingClient, isEntitled } from "@token-gesture/billing"; const billing = new BillingClient({ baseUrl: process.env.BILLING_API_URL!, apiKey: process.env.BILLING_API_KEY!, subject: "organisation", }); const sub = await billing.subscriptions.get({ organisationId }); if (isEntitled(sub)) { // active, trialing, or past_due grantAccess(sub.plan_slug); }
The terms most gateways bury in schedule B.
Every high-risk processor holds a reserve. The difference worth choosing on is whether you were told the number before you integrated.
| Term | Standard | Elevated | Scale |
|---|---|---|---|
| Rolling reserve | 5% | 10% | From 0% |
| Reserve held for | 180 days | 180 days | Negotiated |
| Payout schedule | Weekly, T+7 | Weekly, T+14 | Daily available |
| Settlement currency | AUD EUR USD | AUD EUR USD | + NZD JPY |
| Chargeback ratio ceiling | 0.90% | 1.40% | Per acquirer |
| If the ceiling is breached | Remediation plan | Remediation plan | Dedicated risk lead |
Send us the file everyone else declined.
Tell us your category, your monthly volume and your last twelve months of chargebacks. We'll tell you within four business days whether we can board you, and at what rate.
Pricing
Priced on your risk file, not your category code.
High-risk rates are higher than mainstream rates. Anyone telling you otherwise is quoting a rate they'll reprice after your first dispute cycle. Here is the whole schedule, including the fees that aren't a percentage.
Standard
Clean processing history, chargeback ratio under 0.9%, and a category we already board.
- 5% rolling reserve, released at 180 days
- Weekly settlement, T+7
- Two acquirers on file for cascading
- Pre-chargeback alerts included
- Email support, one business day
Elevated
A prior termination, a MATCH listing, a ratio above 0.9%, or a category that needs a licence on file.
- 10% rolling reserve, released at 180 days
- Weekly settlement, T+14
- Three acquirers, jurisdiction-routed
- Dispute responses drafted with you
- Named risk contact, shared Slack
- Repriced to Standard after six clean cycles
Scale
Above A$500k monthly, or you need settlement terms your treasury can plan around.
- Reserve negotiated, 0% achievable
- Daily settlement available
- All five settlement currencies
- Interchange detail on every statement
- Dedicated risk lead, quarterly review
What a month actually costs
Per-transaction fees and chargeback fees are where a high-risk quote stops matching its headline rate. Both are included here.
The rest of the schedule.
These are the line items that turn a 6.9% quote into an 11% invoice elsewhere. Ours are flat, listed here, and billed on a statement you can export.
| Item | Fee | When it applies |
|---|---|---|
| Account setup | A$0 | Never. Underwriting is free. |
| Monthly minimum | A$0 | No floor, no idle account fee. |
| Chargeback | A$25 | Per dispute received, win or lose. |
| Pre-chargeback alert | A$9 | Per alert actioned — cheaper than the dispute it prevents. |
| Refund | A$0.45 | The per-transaction fee is retained; the discount rate is returned. |
| Retrieval request | A$12 | An issuer asks for documentation on a transaction. |
| Cross-border assessment | 0.8% | Card issued outside your settlement region. |
| Currency conversion | 1.4% | Only when the sale currency differs from your settlement currency. |
| Payout | A$0 | Any schedule, any of the five currencies. |
Not sure which card you'd land on?
Most merchants who expect Elevated get Standard. Send the file and find out — it costs nothing and never touches consumer credit.
Developers
A typed client, a signed webhook, and honest documentation about both.
The service API is the surface your backend calls on a customer's behalf, authenticated with an X-API-Key header. It authenticates the service, not a person — so it belongs on a server and never in a browser bundle.
// npm install @token-gesture/billing — Node 20+, no dependencies import { BillingClient } from "@token-gesture/billing"; const billing = new BillingClient({ baseUrl: process.env.BILLING_API_URL!, apiKey: process.env.BILLING_API_KEY!, subject: "organisation", // or "user" — see below product: ["free", "pro", "team"], }); const plans = await billing.plans.list(); const team = plans.find((p) => p.slug === "team-monthly"); const { checkout_url } = await billing.checkout.create({ organisationId, plan: team!, // the object, so a free plan is caught here currency: "AUD", // AUD · NZD · USD · EUR · JPY }); redirect(checkout_url);
import { verifyWebhook } from "@token-gesture/billing/webhooks"; app.post("/webhooks/billing", express.raw({ type: "*/*" }), (req, res) => { let event; try { event = verifyWebhook({ body: req.body, // the bytes, not a parsed object signature: req.headers["x-webhook-signature"], secret: process.env.BILLING_WEBHOOK_SECRET!, }); } catch (err) { // reason: missing_signature | bad_signature | stale | malformed return res.sendStatus(400); } switch (event.type) { case "subscription.activated": grantAccess(event.data); break; case "subscription.renewed": extendPeriod(event.data); break; case "subscription.expired": revokeAccess(event.data); break; case "payment.charged_back": flagForReview(event.data); break; } res.sendStatus(200); // delivery is one attempt — 200 fast, work after });
# Create a checkout for a customer curl -X POST https://payments.token-gesture.com/api/service/checkout \ -H "X-API-Key: $BILLING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "org_7f3a91", "plan_slug": "team-monthly", "currency": "AUD" }' # → 200 OK # { # "checkout_url": "https://secure.tg-pay.com/fp/s/48812?sign=…", # "subscription_id": "sub_01JQ8M2X", # "expires_at": "2026-09-13T04:22:11Z" # } # Read one customer's subscription curl https://payments.token-gesture.com/api/service/subscriptions/org_7f3a91 \ -H "X-API-Key: $BILLING_API_KEY"
// A page of accounts is one request, not one request per account. // Answers a Map holding only the subjects that have a subscription. const found = await billing.subscriptions.bulk(organisationIds); const paying = organisationIds.filter((id) => found.has(id)); // The server refuses more than 200 distinct subjects rather than // truncating: an answer about the first 200 of your 300 would report // the rest as unsubscribed and you could not tell. Page instead. for (const page of chunk(organisationIds, 200)) { const found = await billing.subscriptions.bulk(page); … }
Eight endpoints. That's the whole surface.
The admin API belongs to the gateway's own console, and the user API only your customer's browser can reach. Your backend uses these.
| Method | Path | Returns |
|---|---|---|
| GET | /plans | A { products, plans } envelope — every product's plans, unfiltered. |
| GET | /subscriptions/{subject} | 200 with subscription: null when there is none. Not a 404. |
| POST | /subscriptions/bulk | Up to 200 subjects per request. Absent means unsubscribed. |
| POST | /checkout | A signed hosted-page URL and the pending subscription id. |
| POST | /subscriptions/{id}/cancel | Cancels at period end; access stays intact until then. |
| POST | /subscriptions/{id}/reactivate | Clears a pending cancellation. |
| POST | /subscriptions/{id}/change-plan | Always schedules for the period end. Upgrades included. |
| GET | /statements/{subject} | Billing history; /statements/detail/{id} for one. |
Every state change we can send you.
Each event maps to exactly one transition in the subscription state machine. Handle the four marked required and you have a correct integration; the rest are reporting.
| Event | What happened | State change |
|---|---|---|
| INITIAL required | First subscription payment approved | → active |
| REBILL required | Recurring payment succeeded | period extended |
| CANCEL | Cancelled by the customer, you, or support | → cancelling |
| UNCANCEL | Cancellation reversed | → active |
| UPGRADE | Moved to a higher tier | plan replaced |
| DOWNGRADE | Next rebill price lowered | plan pending |
| EXPIRY required | Paid period ended after a cancellation | → expired |
| CREDIT | Refund issued | refund recorded |
| CHARGEBACK required | Dispute filed with the issuer | flagged |
| EXTEND | Extra days granted as a goodwill gesture | period extended |
| PURCHASE | One-off, non-recurring charge | purchase recorded |
Six things that will bite you.
Every API has these. Most vendors let you discover them in production.
Changes always schedule
changePlan writes a pending plan that lands at the period end — upgrades included. Don't tell someone their upgrade is live and don't grant it locally, because the next read still reports the old plan.
No subscription is a 200, not a 404
An account with nothing returns subscription: null inside an envelope. Treat a 404 as "no subscription" and you'll also treat an outage as one, quietly revoking access for everybody.
Free is the absence of a subscription
A zero-priced plan can't be checked out and can't be changed to — both are refused. Sending one to the processor builds a hosted page for nothing, and the customer sees a payment failure for a plan that costs no money.
The signature carries no timestamp
A captured request can be replayed for as long as the secret lives. verifyWebhook enforces a freshness window from the event body and rejects with reason: "stale". Rotate the secret if a log ever leaked.
Delivery is a single attempt
There's no retry queue yet. Return 200 before you do the work, and reconcile against subscriptions.bulk on a schedule rather than trusting that every webhook landed.
subject is the whole integration
Our id is opaque — the gateway doesn't know whether the string is a person or an account. Declare subject at construction and passing the wrong id becomes a compile error instead of an incident.
Build against the sandbox today.
Test keys and a full event simulator are issued the moment you create an account. You don't need to be approved to integrate — only to take live money.
Apply for underwriting
Start with the file, not a credit card.
High-risk boarding is an application, not a signup form. Six fields now; the documents come after we've told you we can board you.
Already boarded? Sign in to the console →