Tapaya
Online PaymentsIntegration GuideHosted Checkout

Hosted Checkout

Create a checkout session, redirect the customer, and verify payment from your server.

Send the customer to a Tapaya-hosted payment page for cards, Apple Pay, and Google Pay, then verify the result on your server. Your page never handles card details, and you add no card fields to your shop.

Your frontend
Your backend
Tapaya
  1. 1. Your backend to Tapaya: Create session.
  2. 2. Tapaya to Your backend: Session ID and URL.
  3. 3. Your backend to Your frontend: Redirect to Checkout.
  4. 4. Your frontend to Tapaya: Open Checkout and pay.
  5. 5. Tapaya to Your frontend: Return to your shop.
  6. 6. Your backend to Tapaya: Retrieve session.
  7. 7. Tapaya to Your backend: Payment status.
  8. 8. Your backend to Your frontend: Show verified result.
Keep the Secret key on your backend. A browser return does not prove payment. Your backend checks the order, amount, currency, and successful status before fulfillment. Retrieve the status even if the customer never returns.

Before you start

Pick the key that matches who runs your backend. Keep either key on your server; Checkout Security lists the server-side rules.

Then, in Checkout in the Tapaya Platform:

  1. Add your merchant and check that it shows Ready. See Checkout settings.
  2. Register your shop as an allowed website, with default success and cancel URLs, for the key you chose. The examples use https://shop.example.

Return URLs follow the key

Merchant-key sessions use the merchant's return URLs. Sessions created with the organization Secret key use the organization's return destinations instead. The two are never combined.

Accept a payment

All three options create the same Checkout sessions with the same credentials. Pick one; every step below follows your choice.

Set up your server

Set TAPAYA_CHECKOUT_API_URL to https://api.sandbox.tapaya.com and store your key in TAPAYA_CHECKOUT_API_KEY. Call the API from your server runtime's fetch, never from the customer's browser. The examples read these values through process.env; adapt that lookup if your runtime uses a different secrets interface.

Create a helper that checks HTTP errors before returning the response body:

server/checkout.js
const apiUrl = process.env.TAPAYA_CHECKOUT_API_URL;
const apiKey = process.env.TAPAYA_CHECKOUT_API_KEY;

async function checkoutRequest(path, options = {}) {
	const response = await fetch(new URL(path, apiUrl), {
		...options,
		headers: {
			...options.headers,
			Authorization: `Bearer ${apiKey}`,
		},
		redirect: 'error',
		cache: 'no-store',
		signal: AbortSignal.timeout(8000),
	});

	if (!response.ok) {
		throw new Error(`Checkout request failed (${response.status})`);
	}

	return response.json();
}

Create a session

Calculate the order total on your server, including tax and shipping. Persist the order and a unique checkoutAttemptKey before making the request, and load order from that stored snapshot. merchantOrderId is required.

const session = await checkoutRequest('/merchant/checkout-sessions', {
	method: 'POST',
	headers: {
		'Content-Type': 'application/json',
		'Idempotency-Key': order.checkoutAttemptKey,
	},
	body: JSON.stringify({
		merchantOrderId: order.id,
		amount: order.totalInMinorUnits,
		currency: order.currency,
		successUrl: 'https://shop.example/checkout/return',
		cancelUrl: 'https://shop.example/checkout/cancel',
	}),
});

The response contains the session's id and hosted url. For item summaries, tax breakdowns, customer details, and every response field, see Create a Checkout Session.

If the request fails or times out, retry with the same key and an unchanged body. Never generate a new key for an uncertain result: a changed body with the same key returns HTTP 409.

OptionRetries automatically
HTTP (fetch), cURLNo. Repeat the request yourself. See Idempotency and errors.
Node.js SDKUp to twice after a connection error, a timeout, or HTTP 429, 502, 503, or 504, with the same key and body. See Idempotency and retries. Across separate calls or restarts, pass your persisted key: omitting it generates a new one per call.

Redirect the customer

Persist session.id with your order as its checkoutSessionId, then send the browser to session.url with HTTP 303. Use the URL unchanged rather than constructing it.

// First persist session.id as the order's checkoutSessionId.
return Response.redirect(session.url, 303);

If your framework uses a different response API, send HTTP 303 with session.url in the Location header. With cURL, open the returned url in your browser to test the payment by hand.

Verify the payment

In your return handler, resolve the order from your application's authenticated checkout context and retrieve its stored session from your server. Do this on the cancel URL too: a visit to either URL proves nothing.

const current = await checkoutRequest(
	`/merchant/checkout-sessions/${encodeURIComponent(order.checkoutSessionId)}`,
);

if (
	current.id !== order.checkoutSessionId ||
	current.merchantOrderId !== order.id ||
	current.amount !== order.totalInMinorUnits ||
	current.currency !== order.currency
) {
	throw new Error('Checkout session does not match the order');
}

const paid = current.paymentStatus === 'successful';

Fulfill once, and only when paid

Record the payment and fulfill the order only when the session matches your stored order and paymentStatus is successful, and record it atomically so it happens once. If retrieval fails or the payment is unresolved, keep the session ID and check again. See Payment status.

Handle payment outcomes

Checkout Security covers what to verify and how to retry an unknown result without creating a second session.

OutcomeWhat the session showsWhat to do
Declinedopen, with paymentStatus: failedNothing. The customer can try another card or wallet on the same page.
Not yet knownopen and unpaid while Tapaya confirms the resultRetrieve the session again later.
New session for the same orderThe previous unpaid session reports expiredUse the new session. A paid order, or one with a payment in progress, returns HTTP 409. See One payment per order.
ExpiredStops accepting new payments 30 minutes after creationCreate a new session if the order is still unpaid. A payment submitted before expiry still completes.

No webhooks yet

There are no Checkout webhooks yet. Run a server-side reconciliation job that retrieves sessions for orders whose customers never return.

Check the integration