Tap to Pay Setup
Tap to Pay Setup
Prepare an Android point-of-sale app to accept card-present payments on the merchant's phone. This guide uses these terms:
- POS App: your Android app. It calls the Android POS SDK.
- POS Backend: your server. It holds your secret API key and calls the Synapto server API.
- Softpay: the third-party provider Synapto uses for Tap to Pay.
- Companion app: the Softpay Android app that accepts the contactless card. It is installed separately on the merchant's phone. Your POS App never calls it directly; the SDK does.
- Tap to Pay terminal: a resource Synapto provisions for the merchant's account. It is not a physical card reader.
- Pairing: the one-time link between a Tap to Pay terminal and the companion app installation. Pairing is separate from taking a payment.
When setup is complete, continue with Tap to Pay Payments.
1. Arrange access and install the SDK
Contact Synapto for:
- The SDK and installation instructions.
- Tap to Pay enablement for the merchant's account and a provisioned Tap to Pay terminal for Android.
- The supported device list and the companion app for the environment.
Synapto provisions terminals and releases pairings. There is no partner API for either. The SDK does not install the companion app.
Start in sandbox. The server examples use https://api.synaptopay-sandbox.com and a sandbox secret API key. The POS App must use the same account's pk_sandbox_ key. Keep secret API keys on your POS Backend; see Authentication.
2. Deliver the publishable key (server)
After the merchant signs in to your system, call GetAccountView with a secret API key that has account.read access to the account.
curl https://api.synaptopay-sandbox.com/v1/accounts/acct_YOUR_ACCOUNT:view \
-H "Authorization: Api-Key $API_KEY"Relevant response fields:
{
"account": {
"name": "accounts/acct_YOUR_ACCOUNT"
},
"publishable_key": "pk_sandbox_YOUR_PUBLISHABLE_KEY"
}Return publishable_key to the POS App. The key is also shown in the Dashboard under Settings > Developer.
3. Initialize and check readiness (Android)
The examples import com.synaptopay.pos. Functions named show... and offer... are your own UI hooks. Keep the fields and functions shown here in an object that outlives the Activity, such as a ViewModel; see Handles and concurrency. Call them on the main thread.
SynaptoPos.initialize(applicationContext, publishableKey)SynaptoPos.initialize(getApplicationContext(), publishableKey);private var readinessOperation: OperationHandle? = null
fun checkReadiness() {
if (readinessOperation != null) return
try {
readinessOperation = SynaptoPos.getTapToPayStatus { result ->
readinessOperation = null
if (!result.hasStatus()) {
showReadinessError(checkNotNull(result.error))
} else {
when (checkNotNull(result.status)) {
TapToPayStatus.COMPANION_APP_MISSING -> showInstallSoftpay()
TapToPayStatus.COMPANION_APP_UPDATE_REQUIRED -> showUpdateSoftpay()
TapToPayStatus.SETUP_REQUIRED -> offerPairing()
TapToPayStatus.READY -> showReady()
}
}
}
} catch (error: IllegalStateException) {
showIntegrationError()
}
}private OperationHandle readinessOperation;
void checkReadiness() {
if (readinessOperation != null) return;
try {
readinessOperation = SynaptoPos.getTapToPayStatus(result -> {
readinessOperation = null;
if (!result.hasStatus()) {
showReadinessError(result.getError());
} else {
switch (result.getStatus()) {
case COMPANION_APP_MISSING: showInstallSoftpay(); break;
case COMPANION_APP_UPDATE_REQUIRED: showUpdateSoftpay(); break;
case SETUP_REQUIRED: offerPairing(); break;
case READY: showReady(); break;
}
}
});
} catch (IllegalStateException error) {
showIntegrationError();
}
}initialize throws for an invalid context or key; see Synchronous exceptions.
Request a pairing only after the readiness check returns SETUP_REQUIRED and the merchant chooses to set up Tap to Pay. A missing or outdated companion app, or a readiness error, is not a reason to request a pairing.
4. Create a pairing (server)
CreateTapToPayPairing API reference
Call this endpoint with a secret API key that has payments.write access to the account. Use an idempotency key and keep it, so you can recover the response if it is lost.
curl -X POST https://api.synaptopay-sandbox.com/v1/accounts/acct_YOUR_ACCOUNT/tap-to-pay-pairings \
-H "Authorization: Api-Key $API_KEY" \
-H "Idempotency-Key: YOUR_PAIRING_REQUEST_KEY" \
-H "Content-Type: application/json" \
-d '{"platform": "ANDROID"}'Synapto picks an available Android terminal for the account. You do not choose one. Relevant response fields:
{
"pairing": {
"name": "accounts/acct_YOUR_ACCOUNT/tap-to-pay-terminals/tpt_TERMINAL_ID/pairing",
"state": "PAIRING",
"expires_at": "2026-09-07T12:00:00Z"
},
"setup_token": "ONE_USE_SETUP_TOKEN",
"expires_at": "2026-09-07T12:00:00Z",
"publishable_key": "pk_sandbox_YOUR_PUBLISHABLE_KEY"
}Return the response to the POS App promptly:
| Field | Use |
|---|---|
pairing.name | The pairing reference. Store it on your POS Backend so the app can recover the pairing after a restart or reinstall. It is not secret. |
setup_token | The pairing token. Forward it to the app. Do not log it or store it on your POS Backend. |
expires_at | When the pairing expires. After this time the SDK refuses to open the companion app for it. The token can expire earlier; the companion app then rejects it. |
publishable_key | Must match the key the app initialized with. |
A successful response means the pairing started, not that it completed.
5. Hand off to the companion app (Android)
Call startPairing from the current resumed Activity with the token and reference from the same response. The callback fires when Synapto records the outcome, not when your Activity resumes.
private var pairingWait: PairingWait? = null
fun pair(activity: Activity, pairingToken: String, pairingReference: String) {
if (pairingWait != null) return
try {
pairingWait = SynaptoPos.startPairing(
activity, pairingToken, pairingReference
) { result ->
pairingWait = null
if (!result.hasOutcome()) {
showPairingError(checkNotNull(result.error))
} else {
clearPairingToken()
when (checkNotNull(result.outcome)) {
PairingOutcome.FAILED -> showPairingFailed()
PairingOutcome.SUCCEEDED -> checkReadiness()
}
}
}
} catch (error: IllegalArgumentException) {
showIntegrationError()
} catch (error: IllegalStateException) {
showIntegrationError()
}
}private PairingWait pairingWait;
void pair(Activity activity, String pairingToken, String pairingReference) {
if (pairingWait != null) return;
try {
pairingWait = SynaptoPos.startPairing(
activity, pairingToken, pairingReference, result -> {
pairingWait = null;
if (!result.hasOutcome()) {
showPairingError(result.getError());
} else {
clearPairingToken();
switch (result.getOutcome()) {
case FAILED: showPairingFailed(); break;
case SUCCEEDED: checkReadiness(); break;
}
}
}
);
} catch (IllegalArgumentException | IllegalStateException error) {
showIntegrationError();
}
}clearPairingToken() drops the token your app holds. Keep the token only in process memory while the pairing attempt is active. Clear it when pairing succeeds or fails, or when you abandon the attempt. An operation error does not mean pairing failed. Read the pairing status before retrying; retry only while it is pending and unexpired. Never write the token to saved instance state, storage, logs, or analytics.
The SDK passes the token to the companion app in an intent. On Android 12L and earlier, the platform itself may log intent URIs. If you need stronger protection for the token, require a newer Android version.
To abandon local observation, close the wait, clear the handle, and clear the token:
fun abandonPairing() {
pairingWait?.close()
pairingWait = null
clearPairingToken()
}void abandonPairing() {
if (pairingWait != null) {
pairingWait.close();
pairingWait = null;
}
clearPairingToken();
}Closing the wait does not cancel pairing in Synapto or in the companion app. Read the current pairing status before starting another attempt. A pairing callback already queued for delivery can still run after closing the wait; see Handles and concurrency.
6. Recover or retry pairing
After process death or reinstall: sign the merchant in, initialize the SDK, fetch the pairing reference from your POS Backend, and read its status. Do not request a new pairing until you know the status of the current one.
val pairingRead = SynaptoPos.getPairingStatus(pairingReference) { result ->
if (!result.hasStatus()) {
showPairingError(checkNotNull(result.error))
} else {
when (checkNotNull(result.status)) {
PairingStatus.PENDING -> showPairingPending()
PairingStatus.FAILED -> showPairingFailed()
PairingStatus.SUCCEEDED -> checkReadiness()
}
}
}OperationHandle pairingRead = SynaptoPos.getPairingStatus(pairingReference, result -> {
if (!result.hasStatus()) {
showPairingError(result.getError());
} else {
switch (result.getStatus()) {
case PENDING: showPairingPending(); break;
case FAILED: showPairingFailed(); break;
case SUCCEEDED: checkReadiness(); break;
}
}
});This reads once. It does not resume the wait or open the companion app. For PENDING, read again later with a growing delay.
| Situation | Recovery |
|---|---|
| The pairing-creation response was lost | Repeat the request with the same Idempotency-Key to get the stored response, including the token. A new key creates another pairing and does not return the old token. See Idempotency. |
| The Activity was recreated after the companion app opened | Keep the existing wait. Attach the new screen to its stored state. |
| The Activity was not resumed when the SDK tried to open the companion app | Read the status. If it is PENDING and not expired, call startPairing again with the token you kept in memory. |
The process died, or the token was cleared, or expires_at passed | Read the status. Expiry alone does not mean the pairing failed. If setup stays blocked, contact Synapto. |
Synapto reports FAILED | Clear the token. Fix the cause, then create a new pairing with a new idempotency key. |
| No terminal is available, or an old pairing must be released | Contact Synapto. Repeating the creation request does not release a pairing. |
When pairing succeeds, run a readiness check, then continue to Tap to Pay Payments. If a payment later returns RELEASE_REQUIRED, stop pairing attempts and contact Synapto.
Updated 22 days ago