# Create a session
Source: https://docs.tabby.ai/api-reference/checkout/create-a-session
post /api/v2/checkout
Creates a Checkout session. Creates Session and Payment, returns Pre-Scoring result (status), ids of Payment and Session.
# Session creation payload model
Source: https://docs.tabby.ai/api-reference/checkout/session-payload-model
## Request
The payload example for a session creation request with all required fields for a direct API custom integration. Fields marked `// required` are mandatory for the full checkout session — the background pre-scoring call accepts a minimal subset. Fields marked `// recommended` are optional, but improve scoring accuracy and approval rates — send them whenever you have the data. The `//` comments are annotations, not part of the payload — strip them before sending.
```jsonc theme={"dark"}
{
"payment": { // required
"amount": "100", // required. Up to 2 decimals for UAE and KSA, e.g. 100.00
"currency": "AED", // required. Use the ISO 4217 standard for defining currencies
"description": "test payload",
"buyer": { // required
"name": "John Doe", // required. Customer's full name
"email": "jsmith@example.com", //required. Customer's email address
"phone": "500000001", //required. Customer's phone number
"dob": "2000-01-20"
},
"shipping_address": { // required
"city": "Dubai", // required. Name of city, municipality, or village
"address": "Dubai", // required. Building name, apartment number
"zip": "1111" // required. Postal code
},
"order": { // required
"reference_id": "1001", // required. Merchant-assigned order number.
"updated_at": "2023-11-07T05:31:56Z",
"tax_amount": "0.00",
"shipping_amount": "0.00",
"discount_amount": "0.00",
"items": [ // required
{
"reference_id": "SKU123",
"title": "Name of the product", // required. Name of the product.
"description": "Description of the product",
"quantity": 1, // required. Quantity of the product ordered. Should be >= 1
"unit_price": "0.00", // required. Price per unit of the product. Should be positive or zero.
"discount_amount": "0.00",
"image_url": "https://example.com/",
"product_url": "https://example.com/",
"gender": "Kids",
"category": "Clothes", // required. Name of high-level category (Clothes, Electronics,etc.)
"color": "white",
"product_material": "cotton",
"size_type": "EU",
"size": "M",
"brand": "Name of the Brand",
"is_refundable": true,
"barcode": "12345678",
"ppn": "MNXT2ZM/A",
"seller": "Name of the Seller"
}
]
},
"buyer_history": { // required
"registered_since": "2023-11-07T05:31:56Z", // required. Date and time the customer got registred with you
"loyalty_level": 0, // recommended. Customer's loyalty level within your store
"wishlist_count": 0,
"is_social_networks_connected": true,
"is_phone_number_verified": true,
"is_email_verified": true
},
"order_history": [ // recommended. 5-10 previous orders
{
"purchased_at": "2023-11-07T05:31:56Z", // recommended. Date and time the order was placed
"amount": "100", // recommended. Up to 2 decimals for UAE and KSA, e.g. 100.00
"payment_method": "card",
"status": "new", // recommended. Status of the order
"buyer": { // recommended
"name": "John Doe", // recommended. Customer's full name
"email": "jsmith@example.com", // recommended. Customer's email address
"phone": "500000001", // recommended. Customer's phone number
"dob": "2000-01-20"
},
"shipping_address": { // recommended
"city": "Dubai", // recommended. Name of city, municipality, or village
"address": "Dubai", // recommended. Building name, apartment number
"zip": "1111" // recommended. Postal code
},
"items": [
{
"reference_id": "SKU123",
"title": "Name of the product",
"description": "Description of the product",
"quantity": 1,
"unit_price": "0.00",
"discount_amount": "0.00",
"image_url": "https://example.com/",
"product_url": "https://example.com/",
"gender": "Kids",
"category": "Clothes",
"color": "white",
"product_material": "cotton",
"size_type": "EU",
"size": "M",
"brand": "Name of the Brand",
"is_refundable": true,
"barcode": "12345678",
"ppn": "MNXT2ZM/A",
"seller": "Name of the Seller"
}
]
}
],
"meta": {
"customer": "#customer-id",
"order_id": "#1234"
},
"attachment": {
"body": "{\"flight_reservation_details\": {\"pnr\": \"TR9088999\",\"itinerary\": [...],\"insurance\": [...],\"passengers\": [...],\"affiliate_name\": \"some affiliate\"}}",
"content_type": "application/vnd.tabby.v1+json"
}
},
"lang": "en", // required. Session language
"merchant_code": "code provided to you from Tabby side", // required. Merchant's branch code
"merchant_urls": {
"success": "https://your-store/success",
"cancel": "https://your-store/cancel",
"failure": "https://your-store/failure"
},
"token": null
}
```
## Response
A successful session creation returns `200 OK` with this structure (the `payment` object echoes your request payload back, trimmed here for brevity):
```jsonc theme={"dark"}
{
"id": "e5f9d6c8-...", // session id
"status": "created", // "created" | "rejected" | "expired"
"payment": {
"id": "01a04380-64b8-8432-a20f-904a48584390", // payment id — SAVE IT: used to verify, capture and refund
"created_at": "2026-01-20T09:12:00Z",
"status": "CREATED",
"is_test": true,
"amount": "100.00",
"currency": "AED"
// ...the rest of your request payload echoed back
},
"configuration": {
"available_products": {
"installments": [
{
"web_url": "https://checkout.tabby.ai/?sessionId=...&apiKey=pk_test_...&product=installments",
"qr_code": "https://api.tabby.ai/api/v2/checkout/.../hpp_link_qr?product_type=installments"
}
]
},
"products": {
"installments": {
"type": "installments",
"is_available": true,
"rejection_reason": null
}
}
},
"merchant_urls": {
"success": "https://your-store/success",
"cancel": "https://your-store/cancel",
"failure": "https://your-store/failure"
},
"merchant": {
"name": "Your store name"
}
}
```
What to read from it:
* **`status`** — `created` means the customer can pay; `rejected` means hide Tabby or show the rejection message.
* **`payment.id`** — save it; all later calls (verify, capture, refund) address the payment by this id.
* **`configuration.available_products.installments[0].web_url`** — the Hosted Payment Page link to redirect the customer to. `installments` is an **array**: take the first element. Always validate `web_url` is present before redirecting.
* On rejection (`status: "rejected"`), the reason is in `configuration.products.installments.rejection_reason` (`null` in `created` responses), and `configuration.available_products` comes back **empty** — no `web_url` to redirect to.
# Register a dispute webhook
Source: https://docs.tabby.ai/api-reference/dispute-webhooks/register-a-dispute-webhook
post /api/v1/dispute-webhooks
Registers a dispute webhook for the merchant identified by the secret key and the
`X-Merchant-Code` header. Disputes have no test mode: authorize with a production
secret key (`sk_...`) — a request signed with a test key (`sk_test_...`) is rejected
with `403`. Each `merchant_code` can have up to 4 dispute webhooks; the limit is
separate from payment webhooks. The `url` is normalised before it is stored
(lower-case scheme and host, default port and trailing slash removed) and must be
unique per merchant. `header.value` is a secret: it is accepted in full and returned
masked in every response, including this one — `****` followed by the last 4 characters
for values longer than 8 characters, `****` alone for shorter values.
## Test Your Integration
Place an order with Tabby and refund it (create a Credit Memo) to confirm that the full flow works.
When the test order and the refund go through, you can move to the live environment yourself. Tabby does not run a separate sign-off on your integration.
## Connect to the Live Tabby Environment
Promo only mode
Promotions theme
No price snippet
Webhook Verification Code
Tabby promotions theme
No price snippet
Promo only mode
Multi currency → Checkout → Checkout currency
This page gives you answers to the most common questions about Tabby operating.
401 Not authorized. Error codes for a session creation request can be found here: API Error Codes.
In this case kindly contact Tabby Partner Support at `partner@tabby.ai` (or `partner@tabby.sa` for KSA) or reach out to your assigned Business Manager. When doing so, kindly include:
1. your store name as registered with Tabby;
2. your integration details:
* website URL or app name (for online integrations);
* description of offline integration, if applicable.
### Test Credentials
To test your custom integration with Tabby you need to use the following:
* your testing keys, starting with pk\_test / sk\_test;
* download the Postman API Collection from here: Postman API Collection;
* or use Tabby endpoints provided here: API Playground;
api.tabby.ai for UAE, or api.tabby.sa for KSA (see Base URLs). Payments can be created:
* in test mode using **Test API keys** from merchants' DEV and Stage environments;
* in live mode using **Live API keys**. Live keys are shared after development is completed and QA by Tabby team is done and confirmed.
CANCELLED on Tabby Merchant Dashboard: [https://docs.tabby.ai/api-reference/payments/close-a-payment](https://docs.tabby.ai/api-reference/payments/close-a-payment)
* Capture API call captures the payment, the payment gets status CAPTURED on Tabby Merchant Dashboard: [https://docs.tabby.ai/api-reference/payments/capture-a-payment](https://docs.tabby.ai/api-reference/payments/capture-a-payment)
## How can I verify Payments via Webhooks?
Main article: Webhooks.
There are 3 ways to secure the Webhook notifications:
* allow these IP-addresses for Webhooks:
```
34.166.36.90
34.166.35.211
34.166.34.222
34.166.37.207
34.93.76.191
34.166.128.182
34.166.170.3
34.166.249.7
```
* add static AUTH HEADER during webhook registration: [https://docs.tabby.ai/api-reference/webhooks/register-a-webhook](https://docs.tabby.ai/api-reference/webhooks/register-a-webhook)
* webhooks should be treated as notifications only. To verify the payment status, use the payment\_id from the webhook and call the getPayment API: [https://docs.tabby.ai/api-reference/payments/retrieve-a-payment](https://docs.tabby.ai/api-reference/payments/retrieve-a-payment)
## What should I do if Payments remain in status NEW in Tabby Merchant Dashboard?
Associated articles:
* Payment Statuses;
* Payment Processing.
### Self-Hosted Platforms
Self-Hosted Platforms are: **WooCommerce, Shopify, Magento 2, OpenCart**.
These payments are likely lost (not linked to any created order). Merchants should select one of the options:
* either cancel them (i.e., refund the customer): [https://docs.tabby.ai/api-reference/payments/close-a-payment](https://docs.tabby.ai/api-reference/payments/close-a-payment)
* or manually create an order for such payments, as they are authorized by Tabby, and capture payments: [https://docs.tabby.ai/api-reference/payments/capture-a-payment](https://docs.tabby.ai/api-reference/payments/capture-a-payment)
* or just capture them if the orders exist.
Additionally, merchants should update the plugin to the latest version (following Tabby documentation) and check the plugin settings to prevent similar cases in the future.
### Zid and Salla Platforms
By default, everything is expected to be working successfully when live keys are saved. If you suspect an issue or have lost orders, kindly re-save the keys and contact your platform's support team to verify the situation or register webhooks to prevent such issues.
### Custom (Direct API) Integrations
Merchants may have immediate or delayed captures, meaning NEW statuses may simply be waiting for merchant action. However, in many cases, these payments are also lost.
Merchants should select one of the options:
* either cancel them (i.e., refund the customer): [https://docs.tabby.ai/api-reference/payments/close-a-payment](https://docs.tabby.ai/api-reference/payments/close-a-payment)
* or manually create an order for such payments, as they are authorized by Tabby, and capture payments: [https://docs.tabby.ai/api-reference/payments/capture-a-payment](https://docs.tabby.ai/api-reference/payments/capture-a-payment)
* or just capture them if the orders exist.
Merchants should also check payment logs and investigate integration issues to determine why the payment wasn't acknowledged or captured in time (following Tabby documentation).
The Integrations team can assist with investigating API calls and webhooks - kindly contact them via the integrations thread or an assigned business manager.
### Tabby Card and Payment Links
These are always expected to be Auto-Captured. If they are not - kindly provide your store name with Tabby to Tabby Partner Support `partner@tabby.ai` / `partner@tabby.sa` or to your assigned business manager.
## What is Eligibility check?
Associated articles:
* Background Pre-scoring Check;
* Integration overview.
### How can I know that a customer is not eligible to use Tabby?
In the Tabby Session creation response, you can receive a status like this if a customer is not eligible to use Tabby:
```
"status": "rejected",
"configuration"."products"."installments"."rejection_reason": "not_available"
```
You need to hide Tabby payment option or show it with the General Rejection message or specific reason message depending on the "rejection\_reason" value:
| Reason | English | Arabic |
| - | - | - |
| General Rejection (`not_available`) | Sorry, Tabby is unable to approve this purchase. Please use an alternative payment method for your order. | نأسف، تابي غير قادرة على الموافقة على هذه العملية. الرجاء استخدام طريقة دفع أخرى. |
| `order_amount_too_high` | This purchase is above your current spending limit with Tabby, try a smaller cart or use another payment method | قيمة الطلب تفوق الحد الأقصى المسموح به حاليًا مع تابي. يُرجى تخفيض قيمة السلة أو استخدام وسيلة دفع أخرى. |
| `order_amount_too_low` | The purchase amount is below the minimum amount required to use Tabby, try adding more items or use another payment method | قيمة الطلب أقل من الحد الأدنى المطلوب لاستخدام خدمة تابي. يُرجى زيادة قيمة الطلب أو استخدام وسيلة دفع أخرى. |
For detailed implementation guide check here: Background Pre-scoring Check
### What is the difference between eligibility check and checkout session creation?
Tabby's Checkout API serves two distinct purposes. Here's a side by side comparison:
| Aspect | Eligibility Check | Checkout Session Creation |
| - | - | - |
| **Purpose** | Determine if customer can use Tabby | Create payment session for actual checkout |
| **Timing** | Before showing Tabby payment method | When customer clicks "Place order" |
| **Payload** | Minimal (amount, currency, buyer) | Complete (all order details, items, shipping, merchant URLs) |
| **Response** | Status (`created` or `rejected`) + rejection reason | Status + `web_url` for redirect + `payment.id` |
| **Next Step** | Show or hide / mark unavailable Tabby payment method based on the response | Redirect to Tabby Hosted Payment Page |
### Example of a minimal payload for eligibility check
```json theme={"dark"}
{
"payment": {
"amount": "340.00",
"currency": "SAR",
"buyer": {
"email": "otp.success@tabby.ai",
"phone": "+966500000001"
}
},
"merchant_code": "your_merchant_code"
}
```
**Key principle**: Use the same endpoint (`POST /api/v2/checkout`) but with different payload sizes for different purposes. The minimal payload is accepted **for the background pre-scoring call only** — the session you create when the customer clicks "Place order" must carry the full payload with all required fields.
### When should I perform eligibility check?
Perform the eligibility check **once per checkout session** before displaying payment methods to customer, when you have the basic order details (amount, currency, buyer.phone, buyer.email).
**Best practices:**
* **Payload**: Include as much data as possible (not just minimal payload) for better approval rates and conversion
* **Timing**: Check eligibility when customer enters checkout page with all main data available
* **Error handling**: If API times out or returns error, default to showing Tabby option (fail-safe approach) - never block checkout
* **Two separate calls**: Eligibility check (minimal/recommended data) ≠ Session creation (complete order details when customer clicks "Place order")
## How should I validate checkout session response?
Always validate the checkout session response before redirecting the customer. Here are the critical validation steps:
**Critical Validation Steps**:
1. **Check `status` field** - must be `"created"`. If `"rejected"`, show rejection message and don't redirect;
2. **Validate `web_url` presence** - never redirect without it. Show rejection message if missing;
3. **Store `payment.id`** - required for subsequent capture/refund operations.
For complete validation logic and edge cases check here: Checkout Flow - Response Validation.
## Is it possible to know the rejection reason for payments?
Unfortunately, due to compliance and regulatory restrictions, Tabby is not permitted to disclose the specific rejection reasons to merchants. Customers, however, typically see a rejection message on the Tabby-hosted payment page at the time of the transaction.
If you suspect a technical issue that may be causing multiple or all transactions to be rejected - please, contact Tabby Partner Support at `partner@tabby.ai` (or `partner@tabby.sa` for KSA) or reach out to your assigned Business Manager. When doing so, kindly include:
1. your store name as registered with Tabby;
2. your integration details:
* website URL or app name (for online integrations);
* description of offline integration, if applicable.
## Are there any transaction limits set for my store from Tabby side?
Tabby applies transaction limits based on a combination of factors, including your industry and associated risk levels. Additionally, customer-level limits are dynamically determined based on their historical transaction behaviour with your store and with other merchants using Tabby.
For this reason, we recommend **not setting hardcoded limits** for Tabby transactions on your end, as these may conflict with Tabby's dynamic risk and approval models.
## What should I do if a customer is not redirected back to the website?
When customers finish the Tabby session, they are redirected back to your site from the Tabby Payment Page via one of the three merchant\_urls, with the payment\_id after the separator, e.g. [https://your-store/success?payment\_id=string](https://your-store/success?payment_id=string):
```
"merchant_urls": {
"success": "https://your-store/success",
"cancel": "https://your-store/cancel",
"failure": "https://your-store/failure"
}
```
The merchant\_urls should be included in a Session Creation request sent to Tabby from your side.
## What should I do if I do not receive fields in a response from Tabby API?
If some fields do not return in the response:
* check the endpoint used for the request;
* check the fields format in the request - they should match those specified in the API Reference Documentation;
* check responses in the API Reference Documentation and that these fields are expected to be returned.
## Where can I see if Tabby is working normally?
Status Page Recommendations. Associated article: Technical Requirements -> Status Page.
Recommendations for status page maintenance and incidents:
* Tabby sends alerts through [https://www.tabby-status.com/](https://www.tabby-status.com/). Please, subscribe using your preferred channel for updates.
* Tabby posts alerts manually in real-time within 5-10 minutes of identifying and confirming an issue. The alerts provide details about the affected systems.
* If Checkout or Payments APIs are affected for all countries, we recommend disabling Tabby until the next message confirms the resolution.
* If only one country is affected, you can disable Tabby for that specific country.
* If non-real-time systems are affected, no action is required (incident details will still be posted).
* If you see a Scheduled Technical Maintenance notification, you can plan disabling Tabby for that duration of the maintenance window or mute monitors if the expected timeframe is less than 10 minutes, as it will recover automatically.
* If you suspect an issue with any flow, you can contact Tabby Partner Support `partner@tabby.ai` / `partner@tabby.sa` for more details. However, if you observe that Tabby APIs are not working (e.g., consistently returning 5xx or 4xx errors), please contact the Tabby Integrations Team via the integration thread or via your assigned business manager immediately to investigate and resolve the issue. In the meantime, disable Tabby until the issue is resolved.
## How does Tabby work for shoppers?
Shoppers choose Tabby at checkout — no account is needed beforehand. If they don't have one yet, it is created automatically during their first purchase.
At checkout, the shopper enters their mobile number, verifies it with an SMS code, then reviews and confirms their payment plan before the order goes through.
## Who is eligible to use Tabby?
To be eligible to use Tabby, a shopper must:
* be at least 18 years of age;
* have a valid National ID;
* have a valid debit or credit card to cover payments.
Each purchase is also approved individually — see What is Eligibility check?.
## If a refund is requested, will the fees or Murabaha profit be refunded too?
Yes:
* a **full refund** from the merchant returns the purchase amount plus any fee or Murabaha profit charged on that plan;
* a **partial refund** returns that fee or profit proportionally.
Murabaha profit applies in KSA only.
How a refund changes the customer's downpayment and remaining instalments: Payment Processing → How a Refund Affects the Customer's Instalments.
## General information
For additional support and information, you may also find helpful resources here: Tabby Support Center.
# Quick Start
Source: https://docs.tabby.ai/introduction/quick-start
Choose your integration type below. Use pre-built plugins for popular platforms or implement custom integration via Tabby API.
## E-Commerce Platforms
This page gives you the common knowledge about Tabby operating.
merchant\_code represents a merchant country or a specific store within the country.
### Dates
The
ISO 8601 standard with combined Date and Time in UTC for all API dates. The exceptions to this are dob fields where we accept values in the **YYYY-MM-DD** format.
```JSON theme={"dark"}
{
"registered_since": "2019-08-24T14:15:22Z",
"dob": "2019-08-24"
}
```
### Locale
Operating in the GCC region, Tabby supports the English and Arabic languages and refers to the
RFC 1766 standard.
```JSON theme={"dark"}
{
"lang": "en"
}
```
### String Length Validation
We are processing a maximum of 255 symbols in the **"string"** field.
### Allowed characters in redirect URLs ("success", "cancel", "failure")
1. Latin letters (a-z, A-Z)
2. Arabic letters (ء-ي)
3. Digits (0-9)
4. Special characters - \ | / : ;., + \{}? & @ = # %
## Rate limit
API rate limiting is implemented to maintain stable operations for Tabby services. If an excessive number of requests are sent in a short time, rate limiting may be applied to your requests.
The response will include an HTTP status code 429 error when rate limiting is triggered.
Rate limits are enforced per API Key and are measured on a per-operation basis. Operations are categorized into **Create Session** and **Payment** operations.
* **Live API Keys**: The rate limit is 200 Create Session operations per 10 seconds, while other operations are limited to 100 requests per second.
* **Testing API Keys**: The rate limit is 10 requests per 10 seconds for Create Session operations and 50 requests per second for other operations.
401 Not authorized — see API Error Codes.
## Errors
Tabby APIs use HTTP status codes alongside error objects to handle errors. When an API call fails, Tabby responds with a 4xx/5xx status code and a JSON body containing status: "error", an errorType value, and either a single error message or an errors array for field-level validation failures.
The error value is a human-readable English message to aid in debugging. It is not meant to be displayable to end-users, nor to be machine-readable — log it to assist in debugging, but never parse it. Branch your handling on the HTTP status code and errorType instead.
The full list of errors per endpoint, with example bodies and remediation, lives on the API Error Codes page.
## Supported Browsers & Devices
required in the API Docs, as additional data allows Tabby to increase the AOVs and conversion approval rates.
### Eligibility Check
As a response you receive one of the two session statuses - "created" or "rejected":
* if the session status is "created" - save the **id of the session** (will be required for cancellation step) and **payment.id** (will be required for payment status check and refund steps) received in the response:
```
"status": "created"
"id": "string" // ID of the session
"payment"."id":"string" // ID of the payment
```
* if the session status is "rejected" - show the Payment failure screen and offer the customer an alternative payment method.
"rejection\_reason" field can take the following values, you may optionally add human readable messages for cashier:
| Reason | English | Arabic |
| - | - | - |
| `not_available` | Sorry, Tabby is unable to approve this purchase. Please use an alternative payment method for your order. | نأسف، تابي غير قادرة على الموافقة على هذه العملية. الرجاء استخدام طريقة دفع أخرى. |
| `order_amount_too_high` | This purchase is above your current spending limit with Tabby, try a smaller cart or use another payment method | قيمة الطلب تفوق الحد الأقصى المسموح به حاليًا مع تابي. يُرجى تخفيض قيمة السلة أو استخدام وسيلة دفع أخرى. |
| `order_amount_too_low` | The purchase amount is below the minimum amount required to use Tabby, try adding more items or use another payment method | قيمة الطلب أقل من الحد الأدنى المطلوب لاستخدام خدمة تابي. يُرجى زيادة قيمة الطلب أو استخدام وسيلة دفع أخرى. |
## Customer payment options
* **First option: Send the Payment Link** to the customer via SMS using **send\_hpp\_link API** (provided in a Postman Collection). You can use this method only if you receive a "created" status in the response to the previous request.
* **Second option: use the POS QR Code integration** as a fallback option.
## Payment Processing
Verify the payment status using:
* Webhooks
* or Retrieve Payment API call
### Webhooks
* Tabby sends you a notification payment status update. The initial payment status is CREATED.
* If the Webhook with the authorized or closed status is received - mark the order as successful in your OMS. You can ignore other Webhooks received for this payment.id.
* If the Webhook returns a rejected status - mark the payment as unsuccessful and ask the customer to pay with another payment method.
* If no status is received - the cashier should have an option to cancel the payment.CREATED - the payment has not been completed yet, wait for it to change to one of the terminal statuses.
* AUTHORIZED or CLOSED - a payment was placed successfully, mark orders as successful and proceed with the order on your POS/OMS.
* REJECTED or EXPIRED - a payment is not successful. Ask the customer to pay with a different payment method.
authorized, while Retrieve Request - in upper case: AUTHORIZED.
CREATED. Once canceled - the status will change to EXPIRED.
If the payment has already been authorized, attempting to cancel it will return the following error: 400 Bad Request
```JSON theme={"dark"}
{
"status": "error",
"errorType": "bad_data",
"error": "session is finalized"
}
```
In this case check the payment status via the Retrieve Payment API call and verify the status is AUTHORIZED or CLOSED. Then show a success screen, print a receipt and proceed with the order.
AUTHORIZED, CLOSED, REJECTED or EXPIRED - the session cannot be cancelled.
payment.id by matched payment.order.reference\_id in your OMS.
You can also process a refund from the Tabby Merchant Dashboard.
CLOSED with a captured amount present in the "captures":\[] array of objects can be refunded.CAPTURED.*
OTP:8888 on Tabby Checkout Page.
6. Verify that the successful payment status is received.
**Expected Results:**
1. Session creation response has status "created" - the customer is eligible to use Tabby.
2. A payment link is successfully sent as an SMS to the customer and Tabby Checkout Page opened.
3. Credentials are entered.
4. The success Tabby screen appears.
5. Payment is successful and captured:
* on Merchant Dashboard payment status is CAPTURED
* via a Retrieve Payment API call response Payment status is CLOSED, captured amount is present in the "captures":\[] array of objects.
NEW on the Merchant Dashboard or AUTHORIZED via Retrieve Payment API call - kindly contact your Tabby Account manager or `partner@tabby.ai` / `partner@tabby.sa` to update auto-capture settings.
"rejected" - the customer is **not** eligible to use Tabby.
* **Optionally**: one of the rejection reasons can be shown to cashier.
### 3. Payment Cancellation
**Testing Steps:**
1. From a Cashier's POS choose Tabby.
2. Enter payment amount and a real phone number to receive the real payment link.
"created" - the customer is eligible to use Tabby.
2. A payment link is successfully sent as an SMS to the customer, Tabby Checkout Page opens.
3. A session is cancelled.
4. On checking Payment Status via Retrieve Payment API call it should be EXPIRED.
"EXPIRED" after **session expiry timeout + 5 minutes** (20 + 5 by default). After that the payment will remain in status "EXPIRED", no need to check it further.
OTP:8888 on Tabby Checkout Page.
6. Verify the payment status via Retrieve Payment API.
**Expected Results:**
1. Session creation response has status "created" - the customer is eligible to use Tabby.
2. A payment link is successfully sent as an SMS to the customer.
3. Tabby Checkout Page opens, credentials are entered.
4. The rejection screen with the message 'We can't approve this purchase' appears.
5. On checking Payment Status via Retrieve Payment API call it should be REJECTED.
## Postman Collection
1. Download the JSON file and import it into Postman.
2. Set your base\_url, secret\_key, merchant\_code, and currency in Collection Variables. See Base URLs for regional domains.
"created" or "rejected":
* if the session status is "created" - save the **id of the session** (will be required for cancellation step) and **payment.id** (will be required for payment status check and refund steps) received in the response:
```
"status": "created"
"id": "string" // ID of the session
"payment"."id":"string" // ID of the payment
"configuration"."available_products.installments[0].qr_code": "string" // QR code link which you can use to get the image of the QR code and show it on the POS screen
"configuration"."available_products.installments[0].web_url": "string" // Session link, if you want to use it to generate your own QR code
```
* if the session status is "rejected" - show the Payment failure screen and offer the customer an alternative payment method. Please, do not proceed with any further steps with Tabby. The rejection might be related to order amount being too high, disabled branch code, or other reasons. The response payload will contain the following:
```
"status": "rejected"
```
## Show QR Code
When the session has status "created" you will receive two links in the response. Use either of them to show the QR Code on the POS screen:
| **Response field** | **What it returns** | **When to use** |
| - | - | - |
| `configuration.available_products.installments[0].qr_code` | A link to a **ready-to-display QR code image** (PNG, 200×200 px) | **Recommended.** Fetch the image with a plain HTTP GET and render it directly on the POS screen — no QR generation library is required |
| `configuration.available_products.installments[0].web_url` | The Tabby Checkout session link | Generate your own QR code from this link if you need a custom size or design |
### Using the Ready-Made QR Image
The qr\_code link points to the GET /api/v2/checkout/\{id of session}/hpp\_link\_qr endpoint, which responds with a black-and-white PNG image (Content-Type: image/png, 200×200 px, under 1 KB):
* Use the link **exactly as returned** in the session response — do not construct or modify it manually, all required query parameters are already included.
* The request requires **no authentication headers**, so the POS application can fetch the image directly, without proxying it through your backend.
* Render the PNG in a native image component (for example, an ImageView on Android-based POS terminals) — no WebView or browser is needed.
### QR Code Display Recommendations
* **Size.** Display the QR code at least **3×3 cm** on the physical screen. When upscaling the 200×200 px image, use integer scale factors with nearest-neighbor (non-smoothing) scaling so the QR modules stay sharp. If your framework only supports smooth scaling, generate your own QR code from web\_url at the native resolution instead.
* **One code per payment.** The QR code is unique for each payment session. Never cache or reuse the image between payments — fetch a fresh one for every new session.
* **Fallback.** If the image fails to load (for example, due to a network error), create a new payment session and show the QR code from the qr\_code link of the new response.
## Payment Processing
Once QR Code is shown, check the payment status using the Retrieve Payment API call. We recommend calling the Retrieve API **every 5 seconds** until a terminal status is received. Alternatively, you can add a **“Check status”** button on the POS terminal to manually check this.
The following statuses can be received:
* CREATED - the payment has not been completed yet, wait for it to change to one of the terminal statuses.
* AUTHORIZED or CLOSED - the payment is successful, mark order as successful, print a receipt.
* REJECTED or EXPIRED - the payment is not successful. Ask the customer to pay with a different payment method.
### Cancel a Payment
A request to cancel a payment is available in the Postman collection.
You can cancel a payment in two cases:
1. The cashier presses the “Cancel” button on the POS.
2. Automatically, after a timeout. The **recommended** period is **300 seconds**, but the timeout should **never be less than 180 seconds**.
The payment can only be canceled if its status is CREATED. Once canceled - the status will change to EXPIRED.
If the payment has already been successful, attempting to cancel it will return the following error: 400 Bad Request
```JSON theme={"dark"}
{
"status": "error",
"errorType": "bad_data",
"error": "session is finalized"
}
```
In this case check the payment status via the Retrieve Payment API call and verify the status is AUTHORIZED or CLOSED. Then show a success screen, print a receipt and proceed with the order.
AUTHORIZED, CLOSED, REJECTED or EXPIRED - the session cannot be cancelled.
payment.id by matched payment.order.reference\_id in your OMS.
You can also process a refund from the Tabby Merchant Dashboard.
CLOSED with a captured amount present in the "captures":\[] array of objects can be refunded.CAPTURED.*
Native Screen POS Journey
OTP:8888 on Tabby Checkout page.
5. Verify that the successful status is received.
**Expected Results:**
1. Session creation response has status "created", and a QR code is shown successfully on the POS screen.
2. Tabby Checkout Page opens from the QR code.
3. Credentials are entered.
4. The success Tabby screen appears.
5. Payment is successful and captured:
* on Merchant Dashboard payment status is CAPTURED
* via a Retrieve Payment API call response Payment status is CLOSED, captured amount is present in the "captures":\[] array of objects.
NEW on the Merchant Dashboard or AUTHORIZED via Retrieve Payment API call - kindly contact your Tabby Account manager or `partner@tabby.ai` / `partner@tabby.sa` to update auto-capture settings.
4xx or 5xx status code.
* **Incomplete response** - session is received but configuration.available\_products.installments\[0].qr\_code and web\_url are not present.
3. Observe POS behavior while the terminal attempts to create the session and display a QR code.
4. Select an alternative payment method on the POS terminal to complete the order.
**Expected Results:**
1. Tabby is present among payment methods on the POS terminal.
2. Session creation does not complete successfully - the POS receives no response, an error status, or an incomplete payload.
3. No QR code is shown; a failure screen or error message is displayed to the cashier, prompting to retry or select an alternative payment method. No payment is created in Tabby.
4. The order can be completed via another payment method on the POS terminal.
### 3. Payment Cancellation
**Testing Steps:**
1. Choose Tabby on the POS terminal and enter the payment amount.
2. Show the QR code on the POS terminal for the customer to scan.
3. Click cancel (Cross icon) on Tabby Checkout page (you may also cancel the session from your POS terminal).
**Expected Results:**
1. Tabby is present among payment methods on POS terminal.
2. Session creation response has status "created", and a QR code is shown successfully on the POS screen.
3. Tabby Checkout Page opens, a session is cancelled. On checking Payment Status via Retrieve Payment API call it should be EXPIRED status. A new session can be created from POS terminal.
OTP:8888 on Tabby Checkout page.
5. Verify the payment status via Retrieve Payment API.
**Expected Results:**
1. Tabby is present among payment methods on POS terminal.
2. Session creation response has status "created", and a QR code is shown successfully on the POS screen.
3. Tabby Checkout Page opens, credentials are entered.
4. The rejection screen with the message 'We can’t approve this purchase' appears.
5. On checking Payment Status via Retrieve Payment API call it should be REJECTED.
## Postman Collection
1. Download the JSON file and import it into Postman.
2. Set your base\_url, secret\_key, merchant\_code, and currency in Collection Variables. See Base URLs for regional domains.
Show rejection message for ineligible customers
"payment"."id" **from the response** - it will be used to verify, capture and refund the payment on the next steps.
"lang", enum "ar" / "en"
* **Session creation request** contains all the required parameters from Tabby API
* **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 Background pre-scoring); 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
* Success scenario is working
* Cancellation scenario is working
* Failure scenario is working
* Corner case 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
* Webhooks are registered 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 "authorized"
* On receiving it you should **trigger a** getPayment request to verify the status of the payment
* If a status is "AUTHORIZED" - a capture request should be triggered from your side
* It is an expected behaviour that webhooks return "authorized" in lower case while getPayment - in upper case: "AUTHORIZED".
* 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 Handling Edge Cases)
* 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 Idempotent Requests)
## 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 Close 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 refund test scenario)
* 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.
# App Promo Messaging
Source: https://docs.tabby.ai/pay-in-4-custom-integration/mobile-apps/app-promo-messaging
Promo messaging in mobile apps: SDK snippet components, or custom snippets with the standard copy and Tabby pop-up URLs.
Tabby SDKs contain ready-made snippet components for the Product, Cart, and Checkout pages of your app — use them whenever possible.
If you build your own promo components instead, use the standard Tabby copy below and open the Tabby "Learn more" pop-up in a webview.
### Promo Text
Use the same copy as the Payment Method Description on the web:
| | English | Arabic |
| - | - | - |
| **Payment Method Description** | 4 payments of XX.XX/mo or up to 12 months. Learn more | 4 دفعات بقيمة XX.XX شهريًا أو حتى 12 شهرًا. اعرف المزيد |
* **XX.XX** is the monthly payment amount with your store currency: order total × 1.015 / 4
* **Learn more** should open the Tabby pop-up with the payment schedule in a webview
### Pop-up URLs and live examples
The pop-up URL pattern, parameters, screenshots and a live demo all live on the main On‑Site Messaging → Custom Promo Snippets page — nothing app-specific there, use it as-is with a webview.
# Integration without SDK (WebView)
Source: https://docs.tabby.ai/pay-in-4-custom-integration/mobile-apps/integration-without-sdk
Open the Tabby checkout in your mobile app without our SDK: how to render it, read the payment status correctly, and recover from corner cases.
If our SDK solutions do not meet your requirements, you can open the Tabby Hosted Payment Page in your own mobile app — in a WebView or the system browser — using backend API calls.
## Choose how to render the checkout
There are two ways to open the checkout. The **payment-status and recovery rules on this page apply to both** — only the rendering and the permissions differ.
This page describes the installation process for Tabby Catalogue in Shopify.
Shopify Product Catalogue in Dashboard
App Permissions
Connection Step
Key Installation
Publishing Step
Here you will find the instruction how to install Tabby Catalogue in Zid.
Make sure your store is published before uploading the items
Tabby Catalogue in Zid App Market
Activation Page
base\_url — `https://api.tabby.ai` (UAE) or `https://api.tabby.sa` (KSA). See Base URLs.
* secret\_key — your Tabby Secret API Key
* merchant\_code — your merchant code
* currency — `AED` or `SAR`
8888.
OTP:8888 on Tabby HPP and redirect to the store's success page.
5. Verify that the capture of the successful payment occurred - check for status CAPTURED on the Merchant Dashboard or Payment status AUTHORIZED and CLOSED via Retrieve Payment API.
**Expected Results:**
1. Store checkout opens.
2. Tabby payment method is available on the checkout page (background pre-scoring passed successfully).
3. Tabby Hosted Payment Page opens.
4. The success Tabby screen appears and redirects to the Success URL; the store's 'Thank you' page appears.
5. Order is captured:
* Payment Status is CLOSED;
* captured amount is present in the "captures":\[] array of objects.
## 2. Background Pre-scoring Reject
**Testing Steps:**
1. Add an item to your shopping cart and proceed to checkout.
2. Fill in all the required fields at checkout with the **Pre-scoring reject** credentials.
3. Attempt to choose Tabby as the payment method.
**Expected Results:**
1. Store checkout opens.
2. Tabby payment method is hidden or marked as unavailable with the message:
* English: Sorry, Tabby is unable to approve this purchase, please use an alternative payment method for your order.
* Arabic: نأسف، تابي غير قادرة على الموافقة على هذه العملية. الرجاء استخدام طريقة دفع أخرى.
3. Tabby is hidden from the payment methods list or marked as unavailable.
## 3. Payment Cancellation
**Testing Steps:**
1. Add an item to your shopping cart and proceed to checkout.
2. Fill in all the required fields at checkout with the **Success** credentials.
3. Choose Tabby as the payment method and click "Place order."
4. Click on the 'Back to Store' button (Desktop) or the left corner cross button (Mobile), and confirm cancellation.
**Expected Results:**
1. Store checkout opens.
2. Tabby payment method is available on the checkout page (background pre-scoring passed successfully).
3. Tabby Hosted Payment Page opens.
4. Redirect to the Cancel URL - store checkout or cart page. Verify that the cart is not emptied, and a text message with a cancellation reason is shown:
* English: You aborted the payment. Please retry or choose another payment method.
* Arabic: لقد ألغيت الدفعة. فضلاً حاول مجددًا أو اختر طريقة دفع أخرى.
## 4. Payment Failure
**Testing Steps:**
1. Add an item to your shopping cart and proceed to checkout.
2. Fill in all the required fields at checkout with the **HPP rejection** credentials.
3. Choose Tabby as the payment method and click "Place order."
4. Finish the payment using OTP:8888 in Tabby HPP.
5. Confirm returning to the store by clicking the 'Back to store' button.
6. Verify the payment status via Retrieve Payment API.
**Expected Results:**
1. Store checkout opens.
2. Tabby payment method is available on the checkout page (background pre-scoring passed successfully).
3. Tabby Hosted Payment Page opens.
4. The rejection screen with the message 'We can’t approve this purchase' appears.
5. Redirect to the Failure URL - store checkout or cart page. Verify that the cart is not emptied, and a text message with a failure reason is shown:
* English: Sorry, Tabby is unable to approve this purchase, please use an alternative payment method for your order.
* Arabic: نأسف، تابي غير قادرة على الموافقة على هذه العملية. الرجاء استخدام طريقة دفع أخرى.
6. Tabby payment method is still available for selection among payment methods on Checkout.
7. No order appears in the Merchant Dashboard (unsuccessful orders are not displayed in the Dashboard), Payment Status REJECTED.
## 5. Corner Case
**Testing Steps:**
1. Add an item to your shopping cart and proceed to checkout.
2. Fill in all the required fields at checkout with the **Success** credentials.
3. Choose Tabby as the payment method and click "Place order."
4. Finish the payment using OTP:8888 in Tabby HPP.
5. Once you see the Success Tabby screen (with the tick), **close the browser tab before the redirection.**
6. Verify that the **capture** of the successful payment occurred - check **order status CAPTURED on the Merchant Dashboard** or Payment status via Retrieve Payment API is CLOSED. Ensure the order is marked **successful** on your side.
**Expected Results:**
1. Store checkout opens.
2. Tabby payment method is available on the checkout page (background pre-scoring passed successfully).
3. Tabby Hosted Payment Page opens.
4. There is no redirection to the Success page as the browser tab is closed before redirection.
5. Payment Status changes to AUTHORIZED, then after capture is triggered, it changes to CLOSED with a captured amount in the "captures":\[] array of objects, triggered by the Webhook notifications or Cron Job.AUTHORIZED **before capture** and CLOSED **after capture**.
AUTHORIZED status on Payments API or NEW status on Merchant Dashboard), you can initiate a **Manual Capture** or **Cancel** on the Merchant Dashboard, or **Capture** or **Close** Payment API Request.
reference\_id) — then perform a genuinely new refund with a **new** reference\_id.
**Expected Results:**
1. The refund is reflected on the OMS without any API errors.
2. The order has Refunded/Partially Refunded status in the Tabby Merchant Dashboard, and the Payment status is CLOSED with the refunded amount in the refunds":\[] array of objects.
3. The retry with the same reference\_id returns the **first refund again** (idempotent replay — no second refund is created; don't mistake it for an accepted new refund). The refund with a new reference\_id creates a second refund, and the cumulative total cannot exceed the captured amount.
## 7. Tabby Checkout Focus Change (Mobile Apps)
**Precondition:** Tabby Checkout is opened using the **Success** credentials at the OTP step.
**Testing Steps:**
1. At the OTP step on Tabby Checkout opened in the Webview/In-App browser, minimize the app by navigating to the home screen.
2. Wait a few seconds and then return to the app.
**Expected Results:**
1. The app correctly recovers the Tabby checkout screen at the same step. No crashes or hangs of the app after the return.
2. No App or Webview issues appear.