Ship tap-to-pay in 12 days. These docs tell you how.
Guides, quickstarts, integration patterns, and the full API reference. Start with the 15-minute quickstart; the rest is here when you need it.
15-minute quickstart
This quickstart takes you from zero to a submitted, requirements-tracked sub-merchant in fifteen minutes. You'll get an API key, submit a sub-merchant over REST, and watch Tapped Onboard provision a Stripe Connect Custom account and report back what's still required. Tapped Onboard v1 is a REST API; language SDKs are planned. Everything in sandbox is free and idempotent.
REST quickstart
Tapped Onboard v1 is a REST API — there's nothing to install. Every request carries a static API key as a bearer token; your integration manager issues one key per environment. Keep keys server-side; never ship them in client code. Language SDKs are planned and will wrap this same API.
Pick an environment
- Test —
https://onboard.sandbox.tapped.cc - Production —
https://onboard.tapped.cc
Authenticate
# Send the key as a bearer token on every call except
# /healthz and the Stripe webhook endpoint.
export TAPPED_API_KEY="sk_test_..."
# Liveness — no auth required
curl https://onboard.sandbox.tapped.cc/healthz
# Any real call — authenticated
curl https://onboard.sandbox.tapped.cc/sub-merchants/acme-coffee \
-H "Authorization: Bearer $TAPPED_API_KEY"
The authoritative, always-current field list and error shapes live in the API reference →, rendered straight from the service's OpenAPI spec.
Your first sub-merchant
Create a sub-merchant with a single POST /sub-merchants. The externalRef you choose is your stable handle and the idempotency key — re-send the same value to update, never to duplicate. Tapped provisions a Stripe Connect Custom account and returns a status plus a RequirementsProfile describing what's still needed.
curl -X POST https://onboard.sandbox.tapped.cc/sub-merchants \
-H "Authorization: Bearer $TAPPED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalRef": "acme-coffee",
"businessType": "individual",
"country": "AU",
"email": "owner@acme.example"
}'
In sandbox you can drive an account all the way to enabled using Stripe's published Connect test values (test identity numbers, a 1901-01-01 date of birth, and Stripe's test bank account/routing numbers). Read the response's requirements.currentlyDue, PATCH the missing fields, then poll until charges_enabled and payouts_enabled are true. The full flow is in Sub-merchants below.
The transaction model
Every interaction with Tapped resolves to a transaction, one immutable record with a scheme-traceable chain from tap to payout. Transactions own their own disputes, refunds, and settlement references.
tap, the NFC contact moment, with cryptogram, BIN, and risk decisionauthorisation, what the scheme approved or declined, with reason codecapture, money moved from issuer; locks in the FX if cross-currencysettlement, the entry in the merchant's Tap Ledger; pays out on a schedule
Sub-merchants
A sub-merchant is one of your platform's customers — a restaurant, a salon, a clinic. Tapped Onboard accepts their data, provisions a Stripe Connect Custom account, and reports back exactly what is still required before the account can charge and get paid. v1 is a REST API; language SDKs are planned, so the flow below uses curl. The full, authoritative field list is in the API reference →.
Send neutral business fields plus an externalRef you choose. That ref is your permanent handle and the idempotency key: re-sending the same ref updates the record instead of creating a duplicate. A company submission carries additional company and representative fields — see the API reference for the complete set.
# Individual
curl -X POST https://onboard.sandbox.tapped.cc/sub-merchants \
-H "Authorization: Bearer $TAPPED_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalRef": "acme-coffee", "businessType": "individual",
"country": "AU", "email": "owner@acme.example" }'
# Company
curl -X POST https://onboard.sandbox.tapped.cc/sub-merchants \
-H "Authorization: Bearer $TAPPED_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "externalRef": "kanpai-makati", "businessType": "company",
"country": "PH", "email": "ap@kanpai.example" }'
Every response — from POST, GET, and PATCH — has the same shape: the account status, the two capability flags, and a RequirementsProfile.
{
"externalRef": "acme-coffee",
"status": "pending_requirements",
"charges_enabled": false,
"payouts_enabled": false,
"requirements": {
"currentlyDue": ["individual.id_number", "external_account"],
"eventuallyDue": ["individual.verification.document"],
"pastDue": [],
"disabledReason": null
}
}
Account status
Drive your integration off status and the requirements — never off timing.
| Status | Meaning | What to do |
|---|---|---|
received | Submission accepted and queued. | Wait; poll for the next state. |
creating | Provisioning the Stripe Connect Custom account. | Wait. |
pending_requirements | The account needs more information before it can transact. | Read currentlyDue and PATCH the missing fields. |
enabled | Fully onboarded. Check the capability flags for what it can do. | Start transacting once charges_enabled/payouts_enabled are true. |
rejected | The PSP declined the account. | Inspect disabledReason; this is terminal. |
errored | Provisioning hit an unexpected error. | Retry the submission or contact support. |
The RequirementsProfile
The requirements object mirrors Stripe's requirements and tells you what to collect and how urgent it is.
| Field | Meaning | Action |
|---|---|---|
currentlyDue | Needed now to keep the account enabled. | Collect and PATCH immediately. |
eventuallyDue | Will be required later (e.g. at a volume threshold). | Gather ahead of time to avoid interruption. |
pastDue | currentlyDue items whose deadline passed; capability may be suspended. | Submit urgently to restore the account. |
disabledReason | Why charges/payouts are disabled, if they are (null when not). | Resolve the underlying requirement, or treat as terminal if the status is rejected. |
As you gather what's in currentlyDue, PATCH it back. The response returns the updated profile; keep going until currentlyDue is empty.
curl -X PATCH https://onboard.sandbox.tapped.cc/sub-merchants/acme-coffee \
-H "Authorization: Bearer $TAPPED_API_KEY" \
-H "Content-Type: application/json" \
-d '{ /* the fields named in currentlyDue — see the API reference */ }'
Poll until status is enabled and both charges_enabled and payouts_enabled are true. Integrator-facing onboarding webhooks are planned; until they ship, polling is the supported path (details →).
curl https://onboard.sandbox.tapped.cc/sub-merchants/acme-coffee \
-H "Authorization: Bearer $TAPPED_API_KEY"
For the complete, always-current field list and error shapes, see the API reference →, rendered live from the service's OpenAPI spec.
Payouts & the ledger
Tap Ledger is a double-entry ledger that owns every cent from authorisation through payout. Ledger entries are queryable, reconcilable, and carry scheme references your finance team can match against scheme reports.
By default, sub-merchants settle T+1 to their own bank account. Platform fees accrue to a platform operating account in the same currency as the transaction. You can read more about payout schedules and currency handling in Tap Ledger.
Webhooks
Tapped emits webhooks for every state transition that matters to a platform, tap approved, dispute opened, payout settled, sub-merchant KYB completed. Webhooks are signed with HMAC-SHA256 and retried with exponential backoff for up to 72 hours.
Full reference: Webhooks →
Pattern: tap at the counter
Common for F&B and retail. The POS app holds an active tab; at close, the server presents the device to the guest; tap triggers taps.capture() directly (no pre-auth). Tips are appended to the authorisation via a single-call capture_mode: "tap_with_tip" pattern that pre-computes the tip menu for the locale.
Pattern: tap at the door
Common for field services and home repair. Pre-authorise at arrival (capture_mode: "pre-auth"); capture on job close. If the job scope expands, call authorisations.increase() in place; no re-tap required up to the pre-approved ceiling.
Pattern: the hotel folio
One authorisation at check-in holds the incidentals cap. Nightly room charges post against the authorisation without capturing. At check-out, the consolidated folio captures in a single call; unused portion of the incidentals auth releases. The entire stay sits under one authorisation_chain_id, a single rail for disputes.