Tillsafe developer docs
Accept stablecoins and crypto on your site. Payers pay addresses that only ever pay you, and nobody else holds your money.
Tillsafe turns "send exactly this much to this address" into an API. You create an invoice on your server, the payer pays it on a hosted checkout, and a signed webhook tells you when it is paid. The money goes to addresses that only ever pay you. Tillsafe never holds it.
Early access. The API is not open to the public yet: keys are issued on request. Request early access. Everything on this site describes the API as it runs today.
The API
- Base URL:
https://api.tillsafe.com. Early access: keys are issued on request. - Authentication:
Authorization: Bearer <secret key>, from your server only. - Format: JSON over HTTPS. Money is always a decimal string.
- Reference: the API reference is generated from the API's own OpenAPI description.
How a payment flows
- Your server creates an invoice with your secret key: a fiat price, and which assets you accept.
POST /v1/invoices - The payer opens the checkout (a redirect, a link, or embedded in your page) with your publishable key. It shows each way to pay, the exact amount, the address and a QR code, and checks on the payer's own device that the address really pays you.
- The payment is detected and confirmed on the chain. The invoice moves from
awaiting_paymenttodetectedand thenpaidonce the chain's finality is reached. - Your server gets a webhook,
invoice.paid, signed with your webhook secret. Fulfil the order then.
The quickstart does all four in about ten minutes, in test mode.
Keys and modes
| Key | Prefix | Where it may live |
|---|---|---|
| Secret key, test | cp_test_sk_ | Your server only. Never in a page, an app or a URL. |
| Publishable key, test | cp_test_pk_ | Anywhere: the checkout and your web pages use it. |
| Secret key, live | cp_live_sk_ | Your server only. |
| Publishable key, live | cp_live_pk_ | Anywhere. |
Test keys see only test-mode objects, on testnets (TRON Nile, BSC testnet and other test networks); live keys see only live objects. Nothing you do with a test key can move real money. A secret key sent in a URL is refused, and a publishable key can only reach the payer-facing checkout endpoints.
What is available
| Part | Status |
|---|---|
| API, hosted checkout, embed loader | Early access: keys issued on request. |
Test mode (testnets, simulate_payment) | Early access, the first thing you get. |
| Live mode (mainnets) | Early access, enabled per merchant. Deposit addresses are your own for now. |
| Dashboard (keys, team, payout address changes) | Early access, not public. |
| Forwarder contracts | Not deployed on a public network yet: test mode only. |
| Bitcoin | Test mode only. |
Conventions
- Money is a decimal string, always with its currency or asset:
{"amount": "30.00", "currency": "USD"}. Never a JSON number, so no amount is ever rounded by a float. - Every
POSTneeds anIdempotency-Keyheader. Repeating a request with the same key and the same body returns the first response (withIdempotent-Replayed: true) instead of doing it twice. The same key with a different body is a409. Keys are kept for 24 hours. - Errors share one shape:
{"error": {"type", "code", "message", "param", "doc_url", "request_id"}}. Branch oncode; showmessageto people. - Webhooks are signed and delivered at least once: verify, then deduplicate on the event id. See webhooks and signatures.
Where to go next
- Accepting USDT on TRON and BNB Chain: the networks most payers use.
- How payments are matched: why a payment is matched by its address, and never by a transaction id someone sends you.
- Security model: non-custodial addresses, and the check the checkout runs on the payer's device.
- Testing with simulate_payment: every payment scenario, without a wallet.
- Underpaid, overpaid, late, refunds and reviews: what happens when a payment is not exactly right.