> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tabby.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Full Testing Checklist

After the integration is completed from your side - the QA will be performed by the Tabby team. To make sure that all the requirements are covered - kindly review the below checklist which contains the points assessed by our side.

If any of the points cannot be applied to your website/application architecture - please, notify us about that in an email thread and this point will be discussed separately.

This page covers **Website (desktop and mobile), iOS and Android** custom (direct API) integrations. When integrating Tabby on the <a href="/introduction/quick-start">E-commerce Platform from the list</a> this section is not applicable.

## API Keys and Environment

* **No secret key client-side**: search your rendered page source and all browser network traffic for `sk_` — the Secret Key must never appear in the frontend. Snippets and the HPP use only the Public Key (`pk_...`) and merchant code
* **Secret Key for all backend calls**: every server-side request (session creation, payments, captures, refunds, webhooks) is authorized with the Secret Key — the Public Key belongs to the frontend snippets only
* All calls use the **base URL matching the merchant's region** — see <a href="/api-reference/overview#base-urls">Base URLs</a>
* Capture, refund and close use the **`/api/v2/` endpoints** (v1 is deprecated)

## On-Site Messaging

* **Product and Cart snippets and pop-ups** are present in accordance with the:
  * <a href="/pay-in-4-custom-integration/on-site-messaging#add-the-snippets">custom integration documentation</a>
  * or the <a href="/pay-in-4-custom-integration/mobile-apps/app-promo-messaging">SDK documentation</a> used
* **Product snippets** are shown for all products, there is no amount limitation on displaying snippets
* **Cart snippet** is shown for all amounts, there is no amount limitation on displaying snippets
* **Cart snippet** amount is updated successfully when changes are performed with the items in the Cart: addition / removal / deletion of the items
* If the store has both **Arabic and English languages** - snippets should be displayed correctly for both of them
* **Website:** snippets should fit the width of a Mobile Web screen and have suitable width for a Desktop Web as well
* *In case your store has several countries*: **Tabby snippets** should be displayed only for countries you have already registered with Tabby
* *If our code is not compatible with yours or you have a non-standard plan*: kindly use one of the <a href="/pay-in-4-custom-integration/on-site-messaging#custom-promo-snippets">following custom snippets</a> after the confirmation from your assigned business manager is received

## Tabby as a Payment Method

* **Payment method name** is present in accordance with the <a href="/pay-in-4-custom-integration/checkout-flow#tabby-on-checkout">documentation</a>
* **Tabby logo** is present near the payment method name
* **Checkout snippet** is displayed under the selected Tabby payment method (recommended), or the **payment method description** matches the approved copy — see <a href="/pay-in-4-custom-integration/checkout-flow#tabby-on-checkout">Tabby on Checkout</a>
* There should be no restrictions on displaying Tabby payment method from your side - this behaviour should be handled by background pre-scoring process
* If the store has both **Arabic and English languages** - Payment method should be displayed correctly for both of them
* *In case your store has several countries*: **Tabby payment method** should be displayed only for countries you have already registered with Tabby

## Checkout

* <a href="/testing-guidelines/testing-credentials#2-background-pre-scoring-reject">Background Pre-scoring</a> check is present and working in accordance with the <a href="/pay-in-4-custom-integration/checkout-flow#background-pre-scoring-check">documentation</a>
* **Website:** when a customer decides to place an order with Tabby - Tabby Checkout is opened in the same browser window
* **Mobile apps:** no control buttons (e.g., X, close, back, etc.) from your app are present on Tabby Checkout
* **Total amount** on Checkout = amount shown on Tabby Checkout
* If the store has both **Arabic and English languages** - language marker is sent correctly in a session creation request: object <code className="text-blue-600 dark:text-blue-300">"lang"</code>, enum <code className="text-blue-600 dark:text-blue-300">"ar"</code> / <code className="text-blue-600 dark:text-blue-300">"en"</code>
* **Session creation request** contains all the required parameters from <a href="https://docs.tabby.ai/api-reference/checkout/create-a-session" rel="noopener noreferrer" target="_blank">Tabby API</a>
* **Session is created only when the customer clicks "Place order"** — check your backend logs: the full-payload `/api/v2/checkout` call is triggered by the Place-order action only, not by opening the checkout page (the background pre-scoring call is separate), and one click produces exactly **one** session
* **Sessions are disposable**: a fresh session is created for every payment attempt — the HPP URL is never cached or reused, and your own checkout timeout is shorter than the HPP session lifetime
* **Price parity**: the order total with Tabby selected equals the total with any other payment method — no Tabby-specific surcharges or reduced discounts
* A `rejected` session is a **business outcome, not an error** — don't log it as a failure or fire alerts. On a pre-scoring reject, hide Tabby or mark it unavailable with the rejection message (per <a href="/pay-in-4-custom-integration/checkout-flow#background-pre-scoring-check">Background pre-scoring</a>); on a reject at session creation, show the rejection message instead of redirecting (there is no `web_url` to redirect to)
* **Cart behaviour**: the cart is kept after cancellation/failure and **cleared after a successful payment**
* If the store has both **Arabic and English languages** — your own redirect/result pages (success, cancel, failure messages) are localized too, not only the snippets
* **Payload data quality**: place a second order with the same registered account and confirm `buyer_history` / `order_history` carry real values (actual registration date, real past orders, ISO-8601 dates) — omit optional fields instead of sending empty strings or placeholders
* <a href="/testing-guidelines/testing-credentials#1-payment-success">Success scenario</a> is working
* <a href="/testing-guidelines/testing-credentials#3-payment-cancellation">Cancellation scenario</a> is working
* <a href="/testing-guidelines/testing-credentials#4-payment-failure">Failure scenario</a> is working
* <a href="/testing-guidelines/testing-credentials#5-corner-case">Corner case</a> is supported — complete the OTP, close the tab before the redirect, and verify the payment is still captured via the webhook path

## Payment Verification and Processing

* <a href="/api-reference/webhooks/register-a-webhook">Webhooks are registered</a> for each `merchant_code` + secret key pair (up to 4 webhooks per pair). To receive webhooks for test payments, register them with your test key (`sk_test_...`)
* After a payment is placed successfully with Tabby you receive a webhook to your registered url with status <code className="text-blue-600 dark:text-blue-300">"authorized"</code>
* On receiving it you should **trigger a** <a href="https://docs.tabby.ai/api-reference/payments/retrieve-a-payment" rel="noopener noreferrer" target="_blank">getPayment</a> request to verify the status of the payment
* If a status is <code className="text-blue-600 dark:text-blue-300">"AUTHORIZED"</code> - a <a href="https://docs.tabby.ai/api-reference/payments/capture-a-payment" rel="noopener noreferrer" target="_blank">capture request</a> should be triggered from your side
  * It is an expected behaviour that webhooks return <code className="text-blue-600 dark:text-blue-300">"authorized"</code> in lower case while <a href="https://docs.tabby.ai/api-reference/payments/retrieve-a-payment" rel="noopener noreferrer" target="_blank">getPayment</a> - in upper case: <code className="text-blue-600 dark:text-blue-300">"AUTHORIZED"</code>.
* A **full amount** must be captured
* **Your webhook endpoint answers `200` fast and tolerates duplicates** — the same event can be delivered twice, and delivery order is not guaranteed (see <a href="/pay-in-4-custom-integration/webhooks#handling-edge-cases">Handling Edge Cases</a>)
* For a test payment you observe the **full webhook sequence**: `authorized` → `authorized` with your capture in `captures[]` → `closed`
* **A capture timeout is not a failure**: if the Capture Request times out, your system retrieves the payment and retries the capture with the **same `reference_id`** — verify no duplicate capture is created and no order is left uncaptured
* Every capture and refund carries a **unique `reference_id`** derived from your order (see <a href="/pay-in-4-custom-integration/payment-processing#idempotent-requests">Idempotent Requests</a>)

## Refunds and Cancellations

* **Refunds validate against the captured amount**, not the authorized one — a refund on an uncaptured payment fails. To release an uncaptured amount use <a href="/api-reference/payments/close-a-payment">Close</a> instead, including the leftover after a partial capture (it is **not** auto-closed)
* **Reusing a `reference_id` replays the first refund** instead of creating a new one — a second, genuine refund needs a new `reference_id`, and the cumulative refunded total cannot exceed the captured amount (see <a href="/testing-guidelines/testing-credentials#6-payment-refund-via-api">refund test scenario</a>)
* Your code reads `captures[]` and `refunds[]` entries **by `created_at`**, never by array position — the order is not guaranteed

If you have any questions considering this Checklist - feel free to contact us in the Integrations thread.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.