Skip to content
Type to search…

Choose a Payment Flow

Choose between stored-card, GoTab Wallet, and Apple Pay checkout based on who owns customer identity and payment submission.

Start with one decision: who should own the customer relationship at checkout? That choice determines which saved cards the customer can use, how you create the tab, and whether your server or a GoTab component submits the charge.

Choose the row that matches the experience you want to provide. Each flow links directly to its implementation path:

FlowChoose it whenCreate the tab withPayment submitted by
Stored cardYour application signs the customer in and displays cards from their integration customer profile.The integration customer’s customerId.Your server, through the Integrations API.
GoTab WalletGoTab should authenticate the customer and display cards from their Wallet account.A supported guest identifier, but no integration customerId; create a payment session after the tab.GoTab Wallet.
Apple PayYou want a standalone Apple Pay button without Wallet sign-in or a saved-card picker.A supported guest identifier, but no integration customerId.The Apple Pay component.

Use one flow for each payment attempt. Wallet and Apple Pay submit the payment themselves, so your server should not also submit a stored-card payment for the same checkout.

Whichever row you choose, treat browser callbacks as progress—not proof that you can fulfill the order. You are done only after your server retrieves the latest tab and confirms that it is closed with no balance due.

An integration customer profile and a GoTab Wallet account can belong to the same person, but their saved cards are separate.

Card ownerHow the customer is identifiedHow cards are managedUsed by
Integration customer profileYour server resolves or creates the customer through /api/integrations/customers.Your checkout uses the Payment SDK to add, list, edit, or delete cards.Stored-card payments submitted by your server.
GoTab Wallet accountThe customer authenticates inside Wallet.Wallet displays and manages the customer’s cards.GoTab Wallet checkout.

Cards from one account type are not available through the other. Choose the account model that matches the checkout experience you want to provide.

Use this flow when your application owns customer sign-in and saved-card selection. It currently requires one saved card to cover the tab’s entire outstanding balance. It does not support partial or split payments, refunds, preauthorizations, item-group payments, or authorization transfers.

Before you build this path, contact GoTab API Support to confirm that the location is eligible. Stored-card payments are available for eligible US locations processing in USD.

  1. Server: Resolve or create the integration customer and retain both customer_id and customer_profile_uuid.
  2. Server: Create the tab with customerId set to customer_id.
  3. Browser: Use the Payment SDK and customer_profile_uuid to add or fetch cards, then let the customer select one payment_method_uuid.
  4. Browser → server: When the customer confirms payment, send only the selected payment_method_uuid and tab identifier to your server.
  5. Server → GoTab: Load the current balance and submit one payment through the Integrations API.
  6. Server: Retrieve the latest tab. Fulfill the order only when the tab is closed with no balance due.

Your checkout sends the selected card’s identifier to an endpoint in your application. Keep the amount and GoTab credentials on your server:

await fetch('/checkout/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tabUuid,
paymentMethodUuid: selectedCard.payment_method_uuid,
}),
});

In this example, /checkout/pay is your endpoint. It authenticates the customer, loads the current tab as tab, and submits one payment with your server-side Integrations API client:

const { paymentMethodUuid } = request.body;
const paymentBody = [{
payAmount: tab.balanceDue,
tipAmount: 0,
displayName: 'Online checkout',
methods: [{
type: 'payment_method',
paymentMethodUuid,
amount: tab.balanceDue,
}],
}];
await gotab.post(
`/api/v2/loc/${locationUuid}/tabs/${tab.tabUuid}/payments`,
paymentBody,
);

Here, gotab represents the authenticated Integrations API client in your server application. Load the tab and its balance on the server instead of accepting an amount from the browser.

The payment endpoint returns the updated tab with HTTP 200 when processing completes. Retrieve the latest tab on your server and complete the order only after confirming that the tab is closed with no balance due.

3D Secure 2 (3DS2) is an additional configuration for eligible stored-card payments. GoTab enables it per location; contact GoTab API Support to confirm availability and activation. The standard request above does not need payment customer context.

Once GoTab confirms that 3DS2 is enabled for a location, collect fresh browser context each time the customer confirms payment and send it to your server with the selected card:

const paymentCustomerContext =
await PaymentsSDK.collectPaymentCustomerContext(clientApiAccessId);
await fetch('/checkout/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tabUuid,
paymentMethodUuid: selectedCard.payment_method_uuid,
paymentCustomerContext,
}),
});

Pass the browser SDK access ID issued to the same integration that owns the stored card. The SDK collects local browser measurements and may also return a device fingerprint from a temporary GoTab-owned frame. Using the helper requires the checkout page’s origin in the integration’s gotab_wallet_allowed_origins setting, even when the integration does not otherwise use Wallet. You can instead construct the same browser context yourself. See Collect browser context for the shared origin registration, manual example, fallback behavior, and Content Security Policy requirements.

On your server, combine those browser values with information from the same checkout request. The example below uses your authenticated customer’s server-side record for emailAddress, the request source for ipAddress, and the incoming headers for acceptHeader and userAgent:

const { paymentMethodUuid, paymentCustomerContext } = request.body;
const customer = {
emailAddress: customerRecord.email,
ipAddress: request.ip,
browser: {
acceptHeader: request.headers.accept,
colorDepth: paymentCustomerContext.browser.colorDepth,
language: paymentCustomerContext.browser.language,
screenHeight: paymentCustomerContext.browser.screenHeight,
screenWidth: paymentCustomerContext.browser.screenWidth,
timezoneOffsetMinutes:
paymentCustomerContext.browser.timezoneOffsetMinutes,
userAgent: request.headers['user-agent'],
},
};
if (paymentCustomerContext.deviceFingerprint) {
customer.deviceFingerprint = paymentCustomerContext.deviceFingerprint;
}
const paymentBody = [{
payAmount: tab.balanceDue,
tipAmount: 0,
displayName: 'Online checkout',
customer,
methods: [{
type: 'payment_method',
paymentMethodUuid,
amount: tab.balanceDue,
}],
}];
await gotab.post(
`/api/v2/loc/${locationUuid}/tabs/${tab.tabUuid}/payments`,
paymentBody,
);

Include customer only after 3DS2 has been enabled for the location. It is a write-only payment property and is accepted on a request containing exactly one payment. If your application runs behind a proxy, configure your framework so request.ip resolves the customer’s original request IP.

Billing address is not part of customer or the payment request. It is not required or submitted unless both 3DS2 and the billing-address requirement are enabled for the location. When required, GoTab uses the address associated with the selected payment method. You can collect it while adding the card or add or modify it on an existing card.

Even with 3DS2 enabled, the card issuer may authenticate a payment without showing anything to the customer. When verification is required, the API returns HTTP 202 with status: "requires_action"; your integration then displays Payment Action. HTTP 202 can also return status: "pending" while the existing attempt is still in progress. Both responses include the current Payment Action handoff and its expiresAt:

type IncompletePayment =
| {
status: 'requires_action';
paymentId: string;
handoff: string;
expiresAt: string;
}
| {
status: 'pending';
paymentId: string;
handoff: string;
expiresAt: string;
};

If your server does not receive the payment response because the request times out or the connection drops, send the same original request body to the same payment endpoint again. The API checks for the existing attempt before creating a payment and returns its current paymentId, handoff, and expiresAt, or its terminal state. Do not build a different payment request for recovery.

ResponseWhat your integration does next
HTTP 200Retrieve the latest tab. Complete the order only after confirming the tab is closed with no balance due.
HTTP 202 with a handoff (requires_action or pending)Retain the handoff while the payment remains incomplete, keep other payment controls disabled, and mount the Payment SDK’s initPaymentAction with it.

Common 3DS2 payment errors are:

ErrorWhat to do
BILLING_ADDRESS_REQUIREDThe location’s 3DS2 billing-address requirement is enabled, but the selected card does not have a complete address. Use initEditMethodForm to add or update it, then retry the payment once.
PAYMENT_CUSTOMER_REQUIREDThe location requires 3DS2 context. Collect it when the customer confirms payment and include the complete server-merged customer object.
INVALID_PAYMENT_CUSTOMERSend one payment and match the documented customer shape exactly. Do not include a billing address or extra properties.
INVALID_CUSTOMER_INITIATED_PAYMENTConfirm that the card belongs to the tab’s customer, the payment covers the full balance, and the location supports this flow.
PAYMENT_PENDINGRetrieve the latest tab and continue checking the existing payment instead of creating another charge.

Use Wallet when you want GoTab to authenticate the customer, present their Wallet cards, and complete the payment inside an embedded checkout.

  1. Server: Create the tab with a supported guest identifier, but without an integration customerId.
  2. Server: Create a payment session for the tab.
  3. Server → browser: Send the payment session ID—not your Integrations API bearer token—to the browser.
  4. Browser: Mount Wallet with the tab UUID and payment session ID. Wallet authenticates the customer and submits the payment.
  5. Browser → server: After Wallet reports success, ask your server to retrieve the latest tab before completing the order.

Wallet displays any required 3D Secure verification and can resume the payment after the customer returns to your checkout. See the GoTab Wallet guide for payment-session setup, checkout and card-management modes, configuration, and recovery behavior.

Use Apple Pay when you want to offer a standalone Apple Pay button without asking the customer to sign in to Wallet or select an integration customer’s saved card.

  1. Server: Create the tab with a supported guest identifier, but without an integration customerId.
  2. Browser: Mount initApplePay with the tab UUID and the registered hostname serving your checkout.
  3. Browser: Let the component submit the payment; do not also submit one through the Integrations API.
  4. Browser → server: After onPaymentSuccess, ask your server to retrieve the latest tab before completing the order.

Apple Pay must be enabled for the location, and the checkout must use a registered HTTPS domain. The button appears only in supported Apple and Safari environments. See Apple Pay in the Payment SDK guide for the browser-side configuration.

All three flows rejoin at the same stop condition: your server has retrieved the latest tab and confirmed that it is closed with no balance due. Before release, verify that your checkout preserves this rule when:

  • A customer clicks the payment control more than once.
  • The payment remains pending or awaits verification.
  • The customer refreshes, leaves, or returns to the page.
  • A browser callback is delayed or never arrives.
  • Server reconciliation fails temporarily.

In every case, keep payment controls disabled while an existing payment may still be active, keep Integrations API credentials on the server, and keep fulfillment blocked until server reconciliation reaches the stop condition.