# Create a session Source: https://docs.tabby.sa/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.sa/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](/pay-in-4-custom-integration/checkout-flow#background-pre-scoring-check) 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](/pay-in-4-custom-integration/checkout-flow#possible-rejection_reason-values). * **`payment.id`** — save it; all later calls ([verify](/api-reference/payments/retrieve-a-payment), 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.sa/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. > [!NOTE] > > This is a public, self-service endpoint. Call `https://api.tabby.ai/api/v1/dispute-webhooks` (`https://api.tabby.sa/api/v1/dispute-webhooks` for KSA) with your **live** secret key and the `X-Merchant-Code` header. How dispute webhooks work, payload and statuses: [Dispute webhooks](/pay-in-4-custom-integration/dispute-webhooks). # Remove a dispute webhook Source: https://docs.tabby.sa/api-reference/dispute-webhooks/remove-a-dispute-webhook delete /api/v1/dispute-webhooks/{id} Removes the specified dispute webhook. # Retrieve a dispute webhook Source: https://docs.tabby.sa/api-reference/dispute-webhooks/retrieve-a-dispute-webhook get /api/v1/dispute-webhooks/{id} Retrieves the specified dispute webhook. `header.value` is returned masked. # Retrieve all dispute webhooks Source: https://docs.tabby.sa/api-reference/dispute-webhooks/retrieve-all-dispute-webhooks get /api/v1/dispute-webhooks Retrieves all dispute webhooks registered for the merchant. Returns an empty array when none are registered. # Update a dispute webhook Source: https://docs.tabby.sa/api-reference/dispute-webhooks/update-a-dispute-webhook put /api/v1/dispute-webhooks/{id} Updates the specified dispute webhook. The request **replaces the whole object**: send both `url` and `header`; omitting `header` removes it. The same URL rules as on registration apply (normalised, unique per merchant). # Approve disputes Source: https://docs.tabby.sa/api-reference/disputes/approve-disputes post /api/v1/disputes/approve Approve disputes (refund money to the customer). Only 20 disputes can be approved within a single request. # Challenge disputes Source: https://docs.tabby.sa/api-reference/disputes/challenge-disputes post /api/v1/disputes/challenge Challenge disputes (request Tabby support to take a look at the case). Only 20 disputes can be challenged within a single request. Only disputes with status 'new' might be challenged. # Get dispute by id Source: https://docs.tabby.sa/api-reference/disputes/get-dispute-by-id get /api/v1/disputes/{disputeId} Returns detailed information about dispute. # Get disputes list Source: https://docs.tabby.sa/api-reference/disputes/get-disputes-list get /api/v1/disputes Returns list of 100 recently created disputes. # Provide evidence for a dispute Source: https://docs.tabby.sa/api-reference/disputes/provide-evidence post /api/v1/disputes/{disputeId}/provide-evidence Provide evidence (text and/or attachments) for a dispute. Used by the merchant to submit proof in response to an evidence request. # Upload attachment Source: https://docs.tabby.sa/api-reference/disputes/upload-attachment post /api/v1/disputes/attachments/upload Upload an attachment. Attachment must be in PNG, JPEG or PDF format and can be up to 5 megabytes in size. Files larger than 5 MB are rejected. # API Reference Documentation Source: https://docs.tabby.sa/api-reference/overview In this part of the documentation all the needed API calls for Tabby Integration can be found and tested: * Checkout - it is a whole process of customer data collection and payment authorization. * Payments - the core of Tabby is a payments flow enabling you to handle payments at your webstore. * Webhooks - serve to ensure a seamless payment verification flow. * Dispute webhooks - notify you when a dispute is opened on your payment or changes its status. * Disputes - help merchants resolve issues with customers orders more efficiently. ## Base URLs Tabby uses region-specific domains. Choose the base URL based on the merchant's operating country: | Region | API Base URL | Checkout / Promo | Merchant Dashboard | | - | - | - | - | | **UAE** | `https://api.tabby.ai` | `checkout.tabby.ai` | `merchant.tabby.ai` | | **KSA** | `https://api.tabby.sa` | `checkout.tabby.sa` | `merchant.tabby.sa` | All API paths and payloads are identical across both domains. Tabby identifies the environment (test or live) based on the API keys used. > [!WARNING] > > **Use the base URL that matches the merchant's region for every call.** Many existing integrations call `api.tabby.ai` for all regions and rely on Tabby-side request routing — but not every endpoint is routed (e.g. payments retrieval and disputes), and cross-region calls can return `404`/`403`. New integrations must pin all calls — session creation, payment retrieval, captures, refunds, webhooks, dispute webhooks, disputes — to the regional base URL. | Contact | UAE | KSA | | - | - | - | | **Partner Support** | `partner@tabby.ai` | `partner@tabby.sa` | | **Customer Support** | `help@tabby.ai` | `help@tabby.sa` | ## OpenAPI Specification To access and download the raw OpenAPI specification file kindly use this: [openapi.yaml](https://docs.tabby.ai/openapi.yaml). You can also view this API specification using [ReDoc](https://redocly.github.io/redoc/?url=https://docs.tabby.ai/openapi.yaml), the same is present below: [Tabby API Swagger UI](https://redocly.github.io/redoc/?url=https://docs.tabby.ai/openapi.yaml) ## Quick Links to Documentation * [Direct API Integration Guide](/pay-in-4-custom-integration/quick-start) * [Testing Credentials](/testing-guidelines/testing-credentials) * [Postman API Collection](/testing-guidelines/postman-api-collections) # Capture a payment Source: https://docs.tabby.sa/api-reference/payments/capture-a-payment post /api/v2/payments/{id}/captures Send a Capture requests for Authorized payments only. If you capture the full payment amount, the payment will be automatically closed with full capture. If you capture partial amount, the payment will remain Authorized until the rest of the amount is captured or Close request sent. # Close a payment Source: https://docs.tabby.sa/api-reference/payments/close-a-payment post /api/v2/payments/{id}/close Closed is the final status of the payment. Your payment is going to be closed automatically if you capture the full amount of the payment. If an order is fully cancelled, please close the payment without capturing it - the customer will be refunded for all paid amount. If only a part of the order is delivered, please capture this part and close the payment – it will mean that another part of the order is not going to be delivered to the customer. # List of all payments Source: https://docs.tabby.sa/api-reference/payments/list-of-all-payments get /api/v2/payments Returns a list of success payments (`AUTHORIZED` or `CLOSED`) you’ve previously created. The payments are returned in sorted order, with the most recent payments appearing first. Each entry in the array is a separate payments object, including the status, any captures and any refunds. If no more payments are available, the resulting array will be empty. Payments might be filtered by creation date. # Refund a payment Source: https://docs.tabby.sa/api-reference/payments/refund-a-payment post /api/v2/payments/{id}/refunds Send a full or partial refund amount request. You can only refund the payment that has Closed status. By default refunds reflect instantly, which means Tabby initiates a refund through payment gateway. # Retrieve a payment Source: https://docs.tabby.sa/api-reference/payments/retrieve-a-payment get /api/v2/payments/{id} Retrieves the specified payment. Returns the entire payment object, including the payment Status, Captures and Refunds objects. # Update a payment Source: https://docs.tabby.sa/api-reference/payments/update-a-payment put /api/v2/payments/{id} Updates the reference_id. You can only use this endpoint to update this 1 field. If you send other fields in the request, they will simply be ignored by tabby. The payment to be updated can have a status of AUTHORIZED or CLOSED. # API Error Codes Source: https://docs.tabby.sa/api-reference/response-codes Errors returned by the Tabby API — status codes, response bodies, and how to handle each one. Check the usual errors the Tabby API returns and what to do about each. ## How errors work When a request fails, Tabby responds with a `4xx`/`5xx` HTTP status code and a JSON error body: ```json theme={"dark"} { "status": "error", "errorType": "bad_data", "error": "could not decode request" } ``` For field-level validation failures the body carries an `errors` array instead of a single `error` string: ```json theme={"dark"} { "status": "error", "errorType": "bad_data", "errors": [ { "field": "$.payment.currency", "code": "invalid_value", "message": "" } ] } ``` * Branch your handling on the **HTTP status code** and **`errorType`**. Possible `errorType` values: `bad_data`, `not_authorized`, `no_permission`, `not_found`, `conflict`; a `500` carries `internal`. * `error` is a human-readable English message to aid debugging — log it, but don't parse it and never show it to customers. * On `500` or a network failure, retry per your retry policy; for [captures](#post-apiv2paymentsidcaptures) and [refunds](#post-apiv2paymentsidrefunds) always retry with the **same idempotency key** — your own unique `reference_id` in the request body, see [Idempotent requests](/pay-in-4-custom-integration/payment-processing#idempotent-requests) — so retries can't duplicate money movement. * Use the base URL that matches the merchant's region: `api.tabby.ai` (UAE) or `api.tabby.sa` (KSA) — see [Base URLs](/api-reference/overview#base-urls). Calling the wrong region returns `403` (see below). * A pre-scoring **rejection is not an error**: `POST /api/v2/checkout` returns `200 OK` with `status: "rejected"`. For handling and the customer-facing rejection messages, see [Show rejection](/pay-in-4-custom-integration/checkout-flow#possible-rejection_reason-values). ## Quick reference | HTTP status | Error code (`errorType`) | | - | - | | 400 Bad Request | `bad_data` | | 401 Unauthorized | `not_authorized` | | 403 Forbidden | `no_permission` | | 404 Not Found | `not_found` | | 409 Conflict | `conflict` | | 429 Too Many Requests | — (rate limit reached, see [Rate limit](/introduction/technical-requirements#rate-limit)) | | 500 Internal Server Error | `internal` (Tabby-side error — retry, monitor the [status page](https://www.tabby-status.com/)) | *** ## `POST /api/v2/checkout` Creates a checkout session. Authenticate with the **Secret Key**: `Authorization: Bearer {secret_key}`. > **400 Bad Request — could not decode request** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "could not decode request" } > ``` > > The JSON is malformed or a parameter has the wrong type. Validate the payload against the [Session payload model](/api-reference/checkout/session-payload-model). > **400 Bad Request — currency / merchant\_code mismatch** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "could not create payment: only '{{}}' is supported, but got '{{}}'" } > ``` > > The `currency` in the payload does not match the country assigned to your `merchant_code` (e.g. a UAE `merchant_code` used with `SAR`). Use the correct currency for the country. > **400 Bad Request — field validation failed** > > ```json theme={"dark"} > { > "status": "error", > "errorType": "bad_data", > "errors": [ > { "field": "$.payment.currency", "code": "invalid_value", "message": "" } > ] > } > ``` > > A field value is invalid — `field` points at the offending path. The same envelope covers unsupported `currency`, wrong formats, and similar per-field issues. > **400 Bad Request — payment validation failed** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "could not create payment: should be positive" } > ``` > > Other validation failures on the payment object (amounts and quantities must be positive, etc.) use the same envelope with a human-readable `error`. Read the `error` field for the specific cause. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized", "error": "merchant is null" } > ``` > > Two possible causes: > > 1. The `Authorization` header is missing or wrong — it must be sent as `Authorization: Bearer {secret_key}`. > 2. The `merchant_code` in the payload doesn't match what the API key is bound to. Check the `merchant_code` you're passing and use the correct one for the environment (live vs sandbox). > **403 Forbidden — no permission** > > ```json theme={"dark"} > { "status": "error", "errorType": "no_permission" } > ``` > > Two possible causes: > > 1. You are passing the `token` parameter, but it is not enabled for your integration. Remove `token` from the payload. > 2. The request hit the wrong regional base URL — the body then also carries `"error": "merchant region does not match cluster region"`. Use the base URL that matches the merchant's region: `api.tabby.ai` (UAE) or `api.tabby.sa` (KSA). > **404 Not Found** > > ``` > 404 page not found > ``` > > The URL path is incorrect. Verify it against the [API reference](/api-reference/checkout/create-a-session). Unsupported HTTP methods also return `404`. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "Internal Server Error" } > ``` > > Retry the request per your retry policy. If all requests keep returning `500`, contact the Tabby Integrations team and consider hiding Tabby temporarily. Follow [tabby-status.com](https://www.tabby-status.com/) for maintenance and incident updates. *** ## `GET /api/v2/payments/{id}` Retrieves the current state of a payment. Successful responses and payment statuses are covered in [Payment statuses](/pay-in-4-custom-integration/payment-statuses). > **400 Bad Request — invalid payment id** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "invalid payment id" } > ``` > > The `payment_id` is not a valid UUID. Fix the format. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized" } > ``` > > The `Authorization` header is missing or wrong. It must be sent as `Authorization: Bearer {secret_key}`. > **404 Not Found — no such payment** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_found", "error": "no such payment" } > ``` > > No payment with this `payment_id` is visible to your API key — either the id is wrong, or the payment belongs to a different store / API key. Check the `payment_id` and that you query with the same key that created the session. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "Internal Server Error" } > ``` > > Retry per your retry policy. If `500` persists, contact the Tabby Integrations team and check [tabby-status.com](https://www.tabby-status.com/). *** ## `GET /api/v2/payments` Retrieves a list of payments filtered by query parameters. Errors use the same envelopes as `GET /api/v2/payments/{id}`: * `400 Bad Request` (`bad_data`) — an invalid query-parameter value: `"error": "failed to decode request"`. * `401 Unauthorized` (`not_authorized`) — missing/wrong `Authorization` header. * `500 Internal Server Error` (`internal`) — retry per your retry policy. *** ## `PUT /api/v2/payments/{id}` Updates a payment (e.g. `reference_id`). Errors use the same envelopes as the other Payments endpoints: * `400 Bad Request` (`bad_data`) — malformed body: `"error": "failed to decode request"`. * `401 Unauthorized` (`not_authorized`) — missing/wrong `Authorization` header. * `404 Not Found` (`not_found`) — `"error": "no such payment"`: wrong `payment_id`, or a payment of a different store / API key. * `500 Internal Server Error` (`internal`) — retry per your retry policy. *** ## `POST /api/v2/payments/{id}/captures` Captures funds from an `AUTHORIZED` payment. > [!TIP] > > Always send an idempotency key — your own unique `reference_id` in the request body (see [Idempotent requests](/pay-in-4-custom-integration/payment-processing#idempotent-requests)) — so that retries after a `500` or `409` do not produce duplicate captures. > **400 Bad Request — invalid amount or body** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "Errors: [invalid_value($.amount):{{}}" } > ``` > > The `amount` has a wrong value — negative, too many decimal places, etc. An empty or truncated request body returns the same envelope with `"error": "EOF"`. > **400 Bad Request — amount exceeds remaining** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "could not capture: could not capture more than not captured yet: {{}}" } > ``` > > The amount is higher than the remaining uncaptured amount. Examples: > > 1. Total 100, capture request 150. > 2. Total 100, one successful capture of 50, another capture for 100. > 3. Total 100, one successful capture of 100, another capture for 100. > > Case 3 is usually an unsafe retry — use an idempotency key instead of re-sending the capture. > **400 Bad Request — payment not authorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "could not capture not authorized payments" } > ``` > > The payment status is not `AUTHORIZED`, so captures cannot be made. Check the payment status via `GET /api/v2/payments/{id}` and act accordingly. > **400 Bad Request — already closed** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "already closed" } > ``` > > The payment is already in a terminal `CLOSED` state — usually a duplicate capture of an already fully-captured payment, or a capture after `close`. Check the payment status; if your first capture actually succeeded, no action is needed. > **400 Bad Request — invalid payment id** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "invalid payment id" } > ``` > > The `payment_id` is not a valid UUID. Fix the format. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized" } > ``` > > The `Authorization` header is missing or wrong. It must be sent as `Authorization: Bearer {secret_key}`. > **404 Not Found — no such payment** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_found", "error": "no such payment" } > ``` > > No payment with this `payment_id` is visible to your API key — wrong id, or a payment of a different store / API key. > **409 Conflict — concurrent request** > > ```json theme={"dark"} > { "status": "error", "errorType": "conflict", "error": "could not acquire lock: payment is locked" } > ``` > > Another concurrent request for this payment is being processed. Retry shortly with the same idempotency key. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "Internal Server Error" } > ``` > > If you send an idempotency key, retry safely per your retry policy. Without one, call `GET /api/v2/payments/{id}` first and check already-applied captures before retrying. If `500` persists, contact the Tabby Integrations team and check [tabby-status.com](https://www.tabby-status.com/). *** ## `POST /api/v2/payments/{id}/refunds` Refunds a captured (`CLOSED`) payment. > [!TIP] > > Always send an idempotency key — your own unique `reference_id` in the request body (see [Idempotent requests](/pay-in-4-custom-integration/payment-processing#idempotent-requests)) — to make refund retries safe. > **400 Bad Request — invalid amount format** > > ```json theme={"dark"} > { > "status": "error", > "errorType": "bad_data", > "errors": [ > { "field": "$.amount", "code": "invalid_value", "message": "money supports only 3 decimal places, but got: 5" } > ] > } > ``` > > The `amount` has a wrong value — too many decimal places for the currency, zero or negative (`"error": "refund amount is less then or equals zero"`), or not a number at all (`"error": "error decoding string 'NaN': can't convert NaN to decimal"`). > **400 Bad Request — amount exceeds refundable** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "refund amount should be less or equal than billed amount" } > ``` > > The refund amount exceeds the still-refundable amount. Examples: > > 1. Captured 100, refund request 150. > 2. Captured 100, one successful refund of 50, another refund for 100. > 3. Captured 100, one successful refund of 100, another refund for 100. > > Case 3 is usually an unsafe retry — use an idempotency key instead of re-sending the refund. > **400 Bad Request — cannot refund payment** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "can not refund payment that has no captures and not closed" } > ``` > > The payment is not `CLOSED` or has no captures. Check the payment status and act accordingly. > **400 Bad Request — already closed** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "already closed" } > ``` > > The payment is in a state that no longer accepts this refund (e.g. it was closed without captures). Check the payment status via `GET /api/v2/payments/{id}`. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized" } > ``` > > The `Authorization` header is missing or wrong. It must be sent as `Authorization: Bearer {secret_key}`. > **404 Not Found — no such payment** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_found", "error": "no such payment" } > ``` > > No payment with this `payment_id` is visible to your API key — wrong id, or a payment of a different store / API key. > **409 Conflict** > > ```json theme={"dark"} > { "status": "error", "error": "payment is disputed" } > ``` > > Two possible causes: > > 1. **Active dispute on the payment** — the body carries `"error": "payment is disputed"`. The refund is blocked until the dispute is resolved; see [Disputes](/pay-in-4-custom-integration/disputes). > 2. **Concurrent refunds on the same payment** — the body carries `"error": "could not acquire lock: payment is locked"`. Another request is being processed; retry shortly with the same idempotency key. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "Internal Server Error" } > ``` > > If you send an idempotency key, retry safely per your retry policy. Without one, call `GET /api/v2/payments/{id}` first and check already-applied refunds before retrying. If `500` persists, contact the Tabby Integrations team and check [tabby-status.com](https://www.tabby-status.com/). *** ## `POST /api/v2/payments/{id}/close` Closes an `AUTHORIZED` payment (e.g. when the order is cancelled before capture). > **400 Bad Request — payment not authorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "only authorized payments can be closed" } > ``` > > Only `AUTHORIZED` payments can be closed. Check the payment status and act accordingly. > **400 Bad Request — payment already expired** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "could not cancel payment: payment was already expired" } > ``` > > The payment was `AUTHORIZED` earlier, but the authorization expired before `close` was called. No action is needed — the customer is not charged; treat the payment as expired. > **400 Bad Request — already closed** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "already closed" } > ``` > > The payment is already `CLOSED` — usually a duplicate `close` call. No action is needed. > **400 Bad Request — invalid payment id** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "invalid payment id" } > ``` > > The `payment_id` is not a valid UUID. Fix the format. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized" } > ``` > > The `Authorization` header is missing or wrong. It must be sent as `Authorization: Bearer {secret_key}`. > **404 Not Found — no such payment** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_found", "error": "no such payment" } > ``` > > No payment with this `payment_id` is visible to your API key — wrong id, or a payment of a different store / API key. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "Internal Server Error" } > ``` > > Call `GET /api/v2/payments/{id}` to check the current status before retrying. If `500` persists, contact the Tabby Integrations team and check [tabby-status.com](https://www.tabby-status.com/). *** ## Webhooks — `/api/v1/webhooks` Applies to `POST /api/v1/webhooks`, `GET /api/v1/webhooks`, `GET /api/v1/webhooks/{id}`, `PUT /api/v1/webhooks/{id}`, `DELETE /api/v1/webhooks/{id}`. Authenticate with the **Secret Key**. > [!NOTE] > > On `api.tabby.ai` a missing or malformed `Authorization` header and an unknown secret key are rejected at the edge with an **empty body** (`400` for a missing header or a non-Bearer scheme, `401` for an unknown key). The JSON bodies below are returned once the request reaches the service (today every such request on `api.tabby.sa` reaches the service). Do not rely on a response body for `400`/`401` authentication errors; branch on the status code. > **400 Bad Request — invalid webhook url** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "create payment webhook: invalid webhook url" } > ``` > > The `url` is not a valid, publicly reachable HTTPS URL. Fix the URL. > **400 Bad Request — webhook already exists** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "create payment webhook: webhook already exists" } > ``` > > A webhook with the same URL is already registered for this merchant. List the registered webhooks with `GET /api/v1/webhooks` instead of re-creating. > **400 Bad Request — max number of webhooks reached** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "create payment webhook: max number of webhooks reached" } > ``` > > The merchant already has the maximum number of registered webhooks. Remove stale ones with `DELETE /api/v1/webhooks/{id}` before adding new. > **400 Bad Request — malformed request** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "json decode: EOF" } > ``` > > The request body is empty or not valid JSON, or the webhook `id` in the path is not a valid UUID. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized", "error": "invalid secret key" } > ``` > > The `Authorization` header is missing or carries a wrong key (`api key not found` / `invalid secret key`). It must be sent as `Authorization: Bearer {secret_key}`. > **404 Not Found — no such webhook** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_found", "error": "no such webhook" } > ``` > > No webhook with this `id` exists for your merchant. List the registered webhooks with `GET /api/v1/webhooks`. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "Internal Server Error" } > ``` > > Retry per your retry policy. If `500` persists, contact the Tabby Integrations team and check [tabby-status.com](https://www.tabby-status.com/). *** ## Dispute webhooks — `/api/v1/dispute-webhooks` Applies to `POST /api/v1/dispute-webhooks`, `GET /api/v1/dispute-webhooks`, `GET /api/v1/dispute-webhooks/{id}`, `PUT /api/v1/dispute-webhooks/{id}`, `DELETE /api/v1/dispute-webhooks/{id}`. Authenticate with the **live Secret Key**; the `X-Merchant-Code` header is required on every request. See [Dispute webhooks](/pay-in-4-custom-integration/dispute-webhooks) for how they work. > [!NOTE] > > On `api.tabby.ai` a missing or malformed `Authorization` header and an unknown secret key are rejected at the edge with an **empty body** (`400` for a missing header or a non-Bearer scheme, `401` for an unknown key); a merchant code that does not belong to the key may also be answered at the edge. The JSON bodies below are returned once the request reaches the service (today every such request on `api.tabby.sa` reaches the service). Do not rely on a response body for `400`/`401` authentication errors; branch on the status code. Always call the base URL of your merchant's region. > **400 Bad Request — code is required** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "code is required" } > ``` > > The `X-Merchant-Code` header is missing. It is required on every dispute-webhook request, even when your secret key maps to a single merchant. > **400 Bad Request — invalid webhook url** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "invalid webhook url" } > ``` > > The `url` points to `localhost`, to a raw IP address, or to a host name that does not resolve in DNS. Use a publicly reachable host name (HTTPS recommended). > **400 Bad Request — webhook already exists** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "webhook already exists" } > ``` > > A dispute webhook with the same URL is already registered for this merchant (on `PUT`: the new URL belongs to another of your dispute webhooks). URLs are compared after normalisation (lower-case scheme and host, default port and trailing slash removed). List the registered webhooks with `GET /api/v1/dispute-webhooks` instead of re-creating. > **400 Bad Request — max number of webhooks reached** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "max number of webhooks reached" } > ``` > > The merchant already has 4 dispute webhooks (the limit is separate from payment webhooks). Remove stale ones with `DELETE /api/v1/dispute-webhooks/{id}` before adding new. > **400 Bad Request — malformed request** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "bad_request" } > ``` > > The request body is empty or not valid JSON, or `url` is missing, empty or not an absolute URL. Send a JSON object with a `url`, as in [Register a dispute webhook](/api-reference/dispute-webhooks/register-a-dispute-webhook). > **400 Bad Request — invalid header** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "bad_request", "errors": [ { "code": "invalid_value", "field": "$.header.title", "message": "empty value" } ] } > ``` > > `header` is present but `title` or `value` is missing, empty or invalid: `title` must be a valid HTTP header name (no spaces, up to 128 characters), `value` up to 1024 characters with no control characters. The `errors` array points at the offending field. Omit `header` entirely if you do not want one. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized", "error": "no accessible merchants found" } > ``` > > The `Authorization` header is missing or wrong (`missing Authorization header`, `missing Bearer scheme`, `api key not found`, `invalid secret key` — the last one when a public `pk_` key is sent), or the `X-Merchant-Code` does not belong to the key you authorized with (`no accessible merchants found` — also returned for an unknown code). Send `Authorization: Bearer {secret_key}` together with one of the merchant codes of that key. > **403 Forbidden — disputes have no test mode** > > ```json theme={"dark"} > { "status": "error", "errorType": "no_permission", "error": "disputes have no test mode" } > ``` > > The request was authorized with a test secret key (`sk_test_...`). Dispute webhooks exist for live payments only — use the live secret key (`sk_...`). > **404 Not Found — webhook not found** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_found", "error": "webhook not found" } > ``` > > No dispute webhook with this `id` exists for your merchant (including one that was already removed or belongs to another merchant), or the `id` is not a valid UUID. List the registered webhooks with `GET /api/v1/dispute-webhooks`. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "internal" } > ``` > > Retry per your retry policy. If `500` persists, contact the Tabby Integrations team and check [tabby-status.com](https://www.tabby-status.com/). *** ## Disputes — `/api/v1/disputes` Applies to `GET /api/v1/disputes`, `GET /api/v1/disputes/{disputeId}`, `POST /api/v1/disputes/{disputeId}/provide-evidence`, `POST /api/v1/disputes/approve`, `POST /api/v1/disputes/challenge`, `POST /api/v1/disputes/attachments/upload`. Authenticate with the **Secret Key**. See [Disputes](/pay-in-4-custom-integration/disputes) for the flow itself. > **400 Bad Request** > > ```json theme={"dark"} > { "status": "error", "errorType": "bad_data", "error": "failed to decode request" } > ``` > > The request body is malformed or a parameter is invalid (e.g. wrong `dispute_id` format, missing required fields). Read the `error` field for the specific cause. > **401 Unauthorized** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_authorized" } > ``` > > The `Authorization` header is missing or wrong. It must be sent as `Authorization: Bearer {secret_key}`. > **404 Not Found — dispute not found** > > ```json theme={"dark"} > { "status": "error", "errorType": "not_found", "error": "dispute not found" } > ``` > > No dispute with this `disputeId` is visible to your API key. Check the id against `GET /api/v1/disputes`. > **409 Conflict (provide-evidence)** > > ```json theme={"dark"} > { "status": "error", "errorType": "conflict" } > ``` > > The dispute is not in a state that accepts this operation — e.g. evidence was already submitted or the dispute has been resolved. Re-fetch it with `GET /api/v1/disputes/{disputeId}` and check its status. > **500 Internal Server Error** > > ```json theme={"dark"} > { "status": "error", "errorType": "internal", "error": "Internal Server Error" } > ``` > > Retry per your retry policy. If `500` persists, contact the Tabby Integrations team and check [tabby-status.com](https://www.tabby-status.com/). # Register a webhook Source: https://docs.tabby.sa/api-reference/webhooks/register-a-webhook post /api/v1/webhooks Creates a new webhook. Webhooks are registered per `merchant_code` and per environment: the environment is determined by the secret key you authorize the request with — a production key (`sk_...`) registers webhooks for production payments, a test key (`sk_test_...`) registers webhooks for test payments. Each `merchant_code` + key pair can have up to 4 webhooks. > [!NOTE] > > This is a public, self-service endpoint. Call `https://api.tabby.ai/api/v1/webhooks` (`https://api.tabby.sa/api/v1/webhooks` for KSA) with your secret key and the `X-Merchant-Code` header. How webhooks work, payload and retries: [Payment webhooks](/pay-in-4-custom-integration/webhooks). # Remove a webhook Source: https://docs.tabby.sa/api-reference/webhooks/remove-a-webhook delete /api/v1/webhooks/{id} Removes the specified webhook. # Retrieve a webhook Source: https://docs.tabby.sa/api-reference/webhooks/retrieve-a-webhook get /api/v1/webhooks/{id} Retrieves the specified webhook. # Retrieve all webhooks Source: https://docs.tabby.sa/api-reference/webhooks/retrieve-all-webhooks get /api/v1/webhooks Retrieves all registred webhooks. # Update a webhook Source: https://docs.tabby.sa/api-reference/webhooks/update-a-webhook put /api/v1/webhooks/{id} Updates the specified webhook. # Magento 2 Plugin Installation Source: https://docs.tabby.sa/e-commerce-platforms/magento-2/magento-2-plugin-installation Install the Tabby extension for Magento 2 with Composer, configure your API keys, and go live. > [!NOTE] > > Tabby works on the Magento 2 platform for all supported countries. The Tabby extension adds Tabby as a payment method at checkout, shows Tabby promotions on your product and cart pages, and can share your product feed with the Tabby Shop. | Requirement | Value | | - | - | | Magento | 2.4.x (`magento/framework ^103.0`) | | PHP | 7.1 or newer | | Access | Composer and command-line access to your Magento project | ## Installation Steps **Step 1** Sign up at [merchant.tabby.ai](https://merchant.tabby.ai/) (KSA: [merchant.tabby.sa](https://merchant.tabby.sa/)) and fill in all your business details. Your test API keys are then available in the Merchant Dashboard under **Business profile → Stores**. **Step 2** [Install the extension with Composer](#install-with-composer) - the recommended way to install it and to update it later. **Step 3** [Configure Tabby as a payment method](#configure-tabby-as-a-payment-method) using your test keys. **Step 4** [Place a test order and refund it](#test-your-integration) to confirm that the full flow works. **Step 5** Replace the test keys with your live keys and [deploy to production](#connect-to-the-live-tabby-environment). > [!WARNING] > > Install and test the extension in a staging environment first, and keep a backup of your Magento files and database so you can roll back. ## Install with Composer `tabby/m2-payments` is a metapackage that pulls both Tabby modules: `tabby/m2-checkout` (payment methods, promotions, webhooks) and `tabby/m2-feed` ([product catalogue](/e-commerce-platforms/magento-2/magento-2-product-catalogue)). ```bash theme={"dark"} composer require tabby/m2-payments php bin/magento module:enable Tabby_Checkout Tabby_Feed php bin/magento setup:upgrade php bin/magento setup:di:compile php bin/magento setup:static-content:deploy -f php bin/magento cache:flush ``` Confirm that both modules are enabled: ```bash theme={"dark"} php bin/magento module:status Tabby_Checkout Tabby_Feed ``` ### Update the extension Run the same sequence with `composer update` to move to the latest release: ```bash theme={"dark"} composer update tabby/m2-payments --with-dependencies php bin/magento setup:upgrade php bin/magento setup:di:compile php bin/magento setup:static-content:deploy -f php bin/magento cache:flush ``` > [!TIP] > > We recommend opening the Tabby settings in the Magento admin after an update and clicking **Save Config**, even if you change nothing. Saving re-applies your configuration to the settings the installed version supports, and registers the webhooks for any store that is missing them. > **Composer reports a higher matching version on packagist.org** > > If Composer stops with *"Higher matching version 7.0.2 of tabby/m2-payments was found in public repository packagist.org"*, your project is pinned to an older constraint. Require the version range explicitly and run the update again: > > ```bash theme={"dark"} > composer require tabby/m2-payments:^7.0 > ``` ## Other Ways to Install Composer is the supported path. Use one of these only if Composer is not available to you. > **Manual installation from an archive** > > 1. Download the [extension archive](https://storage.googleapis.com/plgns/magento2-latest.tar.gz). > > 2. Create the directory `Magento2_root/app/code/Tabby/Checkout/` if it does not exist. > > 3. Unpack the archive and move all files into `Magento2_root/app/code/Tabby/Checkout/`. > > 4. Verify that **Tabby\_Checkout** appears in the list of disabled modules: > > ```bash theme={"dark"} > php bin/magento module:status > ``` > > 5. Enable the module and rebuild: > > ```bash theme={"dark"} > php bin/magento module:enable Tabby_Checkout > php bin/magento setup:upgrade > php bin/magento setup:di:compile > php bin/magento setup:static-content:deploy -f > php bin/magento cache:flush > ``` > > > [!WARNING] > > > > The archive contains the checkout module only. The [product catalogue](/e-commerce-platforms/magento-2/magento-2-product-catalogue) module ships through Composer, and every future update has to be applied by hand. > **Adobe Commerce Marketplace** > > The extension is published as [Tabby M2 Payments](https://commercemarketplace.adobe.com/tabby-m2-payments.html). Add it to your Adobe Commerce account and install it from your Marketplace purchases. Adobe delivers the same Composer package, so the Magento commands in [Install with Composer](#install-with-composer) still apply. ## Configure Tabby as a Payment Method Sign in to your Magento 2 admin portal and go to ***Stores > Configuration > Sales > Payment Methods > Tabby Payments***. In the **Tabby Api** section: | Setting | Value | | - | - | | Merchant Public Key, Merchant Secret Key | Your test keys from the Merchant Dashboard | | Aggregate store by currency | **Yes** - the extension derives the merchant code from each store's base currency (`AE`, `SA`), which are the default codes Tabby registers for your account. Set it to **No** only if you use [your own store codes](#use-your-own-magento-store-codes). | | Capture on | **Order placed**, unless your account manager agreed on a different moment | | Debug | **Yes** while you are testing | Then enable the payment method in the **Tabby Checkout - Split in 4** section and click **Save Config**. Saving validates your keys and registers your webhooks with Tabby. > [!NOTE] > > If the changes do not take effect immediately, go to ***System > Cache Management***, click **Flush Magento Cache**, then sign out and sign back in to reset permissions. ### Manage Tabby Product Catalogue Feature The [product catalogue](/e-commerce-platforms/magento-2/magento-2-product-catalogue) lists your products on the Tabby Shop. Enable it in ***Stores > Configuration > Sales > Payment Methods > Tabby Payments > Tabby Feed***. > [!WARNING] > > The Tabby Product Catalogue feature works with **live API keys** only. ![Tabby Feed setting](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/m2-tabby-feed.avif?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=93366cf099976d439e74ddab4060e280) ## 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 **Step 1** Copy the live Public and Secret keys from the Merchant Dashboard under **Business profile → Stores**. If the live keys are not shown there yet, ask Tabby Partner Support at `partner@tabby.ai` (or `partner@tabby.sa` for KSA) to make them visible. **Step 2** Enter them in ***Stores > Configuration > Sales > Payment Methods > Tabby Payments*** on your production site. **Step 3** Click **Save Config**. Webhooks are registered per key pair, so this save is what creates your live webhooks - do it on production even if you copied the configuration over from staging and the keys are already in place. ## Specific Settings ### Promotions Only Mode If your checkout runs through another provider, the plugin can serve Tabby promotions only: it enables the product and cart page snippets and hides Tabby at checkout. Go to ***Stores > Configuration > Sales > Payment Methods > Tabby Payments***, and set ***Tabby plugin mode*** to ***Promotions only***: ![Promotions only mode](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/m2-promotions-only.avif?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=18a38ae9ef7dfad756f6e1623ed52c43) Promo only mode ### Use Your Own Magento Store Codes By default there is nothing to send us: with **Aggregate store by currency** set to **Yes**, the extension derives the merchant code from the store's base currency (`AE` for AED, `SA` for SAR) and Tabby registers those codes for your account. Use your Magento store codes instead when you need a separate Tabby merchant code per store group, for example for separate reporting or separate payment plans per brand: **Step 1** In the Magento admin, go to ***Stores > All Stores*** and copy the **Code** of every store group you sell with. ![Store codes](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/m2-stores.avif?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=76d37b177006716bda79b976d5f3e12d) **Step 2** Ask Tabby Partner Support at `partner@tabby.ai` (or `partner@tabby.sa` for KSA) to set them up as custom merchant codes, for example `website_store_ae`. Wait for confirmation that the codes are registered on Tabby's side. **Step 3** Set **Aggregate store by currency** to **No**. The extension now sends the store group code as the merchant code. > [!WARNING] > > Change this setting only after Tabby confirms that your codes are registered. A code Tabby does not know breaks checkout with an authorization error. ### Sell in Several Currencies If one store group sells in more than one currency, enable **Use local store currency on checkout**. The extension then charges in the currency the customer sees and appends it to the merchant code, for example `SA_SAR`. These codes have to be registered on Tabby's side too, so ask Tabby Partner Support to set them up before you turn this on. # Magento 2 Product Catalogue Source: https://docs.tabby.sa/e-commerce-platforms/magento-2/magento-2-product-catalogue Enable the Magento 2 Product Catalogue feature to list your store's products on the Tabby Shop. We've introduced an exciting new feature to the Tabby Magento 2 plugin – the **Product Catalogue Feature**, designed to help you list your store’s products seamlessly on the Tabby Shop. The feature is enabled by default in new plugin versions starting from 5.0.11. [Update the plugin](/e-commerce-platforms/magento-2/magento-2-plugin-installation#update-the-extension) with Composer: ```bash theme={"dark"} composer update tabby/m2-payments --with-dependencies php bin/magento module:enable Tabby_Checkout Tabby_Feed php bin/magento setup:upgrade php bin/magento setup:di:compile php bin/magento setup:static-content:deploy -f php bin/magento cache:flush ``` Check that the setting is enabled in the Tabby API options located at:\ **Stores -> Configuration -> Sales -> Payment methods -> Tabby Payments -> Tabby Feed -> "Share product feed with Tabby"** ![Tabby feed parameter](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/m2-tabby_feed_5-0-11.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=2d2f833243bafed1c8df34e34e6f1000) **Key features:** * **Automated Product Listing**: Easily list your entire Magento 2 store inventory on the Tabby Shop without manual intervention. * **Real-Time Stock Updates**: Keep your product listings up-to-date effortlessly, as the plugin automatically syncs inventory availability and reflects out-of-stock items in real time. * **Increased Visibility**: Showcase your products to a broader audience by making them visible to all Tabby app users. * **Traffic Generation**: Drive interested shoppers directly to your Magento 2 store, increasing clicks and potential sales. # Matjrah Source: https://docs.tabby.sa/e-commerce-platforms/matjrah Connect Tabby on the Matjrah platform by entering your API keys. > [!NOTE] > > Tabby works on the Matjrah platform for the countries KSA and UAE. 1. [Register with Tabby](https://merchant.tabby.ai/) (KSA: [merchant.tabby.sa](https://merchant.tabby.sa/)) and fill all required business details to collect Tabby API Keys. 2. Fill in the keys using the instruction below: ![Tabby & Matjrah](https://mintcdn.com/tabby-26277sa/hJvAwtn7TTitdYQj/images/matjrah.png?fit=max\&auto=format\&n=hJvAwtn7TTitdYQj\&q=85\&s=d1a509c9c5dbe7bb36de7b7cbea7a0b7) ![Log in to Matjrah](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/matjrah-login.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=d0cef7d541c1c8c3af2e2b1f11076b41) ![Step 1](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/matjrah-1.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=6dcb2cc3c2fb216c80b4eeeb0e58bd90) ![Step 2](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/matjrah-2.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=014a40595616e315e24a5ab10a8ced38) ![Step 3](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/matjrah-3.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=cec74e6a1fd8d776d682c98920ddf132) ![Steps 4 and 5](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/matjrah-4.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=cf4bcc98dcd9a0c7456796f49f579e85) ![Steps 6 and 7](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/matjrah-5.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=f80503082b3f255d29e381ef25238a78) ![Step 8](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/matjrah-6.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=40aecb00f5b372c8d837bdb0421d3e90) ![Contact information](https://mintcdn.com/tabby-26277sa/fJVMkzMpWAVssPgF/images/zid-7.png?fit=max\&auto=format\&n=fJVMkzMpWAVssPgF\&q=85\&s=a26c676509e7599893b96a6175a3d281) # Odoo Plugin Installation Source: https://docs.tabby.sa/e-commerce-platforms/odoo/odoo-plugin-installation Install the Tabby module on Odoo, configure your API keys, and take the integration live. > [!NOTE] > > Tabby works on the Odoo platform for all supported countries. Odoo versions 17, 18, and 19 are supported. > [!WARNING] > > The Tabby module can be installed on self-hosted (on-premise) Odoo and Odoo.sh. Odoo Online (SaaS) does not support the installation of custom modules. ## Installation Steps *** *** *** *** *** *** ### Download the Tabby Module Tabby provides a separate module build for each supported Odoo version. Download the archive matching the major version of your Odoo installation: | Odoo version | Module download | | - | - | | Odoo 19 | [odoo19-latest.zip](https://storage.googleapis.com/plgns/odoo19-latest.zip) | | Odoo 18 | [odoo18-latest.zip](https://storage.googleapis.com/plgns/odoo18-latest.zip) | | Odoo 17 | [odoo17-latest.zip](https://storage.googleapis.com/plgns/odoo17-latest.zip) | Each archive contains the `payment_tabby` module folder — this folder is the unit of installation. ### Install the Odoo Module 1. Extract the downloaded archive and locate the `payment_tabby` module folder inside it 2. Copy the `payment_tabby` folder into your Odoo addons directory — any directory listed in the `addons_path` parameter of your `odoo.conf` (commonly a `custom_addons` folder). Make sure the copied files are readable by the user running the Odoo service 3. Restart the Odoo service 4. Activate the developer mode: go to ***Settings -> General Settings***, scroll down to **Developer Tools** and click **Activate the developer mode** 5. Go to ***Apps*** and click ***Update Apps List*** in the top menu, then confirm the update 6. In the Apps search bar remove the default filters, search for **Tabby** and click **Activate** on the "Payment Provider: Tabby" module > [!NOTE] > > **Odoo.sh:** instead of copying files to the server, add the `payment_tabby` folder to your Odoo.sh project's GitHub repository and deploy the branch — the module then appears in the Apps list after **Update Apps List**. #### Optional: Updating the Odoo Module When a new version of the Tabby module is released: 1. Download the latest archive for your Odoo version and replace the existing `payment_tabby` folder in your addons directory with the new one 2. Restart the Odoo service 3. Go to ***Apps***, remove the default filters, find the **Tabby** module and click ***Upgrade*** from the module's menu ### Configure Tabby as a Payment Method 1. Go to ***Website -> Configuration -> Payment Providers*** (the same screen is also available via ***Invoicing -> Configuration -> Payment Providers*** and ***Sales -> Configuration -> Payment Providers***) 2. Open **Tabby** in the list of providers 3. In the **Credentials** tab, enter the **Tabby Public Key** and **Tabby Secret Key** you received from your account manager 4. Set the **State** to **Enabled** 5. Click **Save** Once the provider is enabled, it is automatically published on your website and Tabby appears among the payment methods on Checkout. > [!NOTE] > > Tabby requires the order currency to match the customer's country: UAE-AED, KSA-SAR. Make sure the currency of your website/pricelist is set accordingly — Tabby will not be displayed on Checkout for unsupported currencies. ### Test Your Integration After completing your integration, make an order and refund it to ensure that everything works as expected. **Coordinate Testing with Tabby** Before deploying Tabby integration to your production site, Tabby needs to validate the correctness of the integration. Contact your account manager to coordinate this test. ### Connect to the Live Tabby Environment 1. Get your Public and Secret keys for the live environment. Ask your account manager to share the keys 2. Set your live keys in the Odoo admin panel: go to ***Website -> Configuration -> Payment Providers -> Tabby***, open the **Credentials** tab and replace the test keys 3. **Launch to Production**\ After you've connected to the live environment and we've tested your integration, you're ready to offer Tabby payment methods to your customers. ### Feedback and Support If you face any issues during installation or testing, don't hesitate to get in touch with `partner@tabby.ai` / `partner@tabby.sa` with the store link, issue description, and screenshots or screencasts of the observed behaviour. # OpenCart Plugin Installation Source: https://docs.tabby.sa/e-commerce-platforms/opencart/opencart-plugin-installation Install the Tabby plugin for OpenCart, configure your API keys, and connect to the live environment. > [!NOTE] > > Tabby works on the OpenCart platform for all supported countries. OpenCart versions 2.x, 2.1.x, and 3.x with the Journal Theme are supported. ## Installation Steps *** *** *** *** *** *** ### Install the OpenCart Extension 1. Go to ***Extensions -> Installer*** and install the extension. 2. Go to ***Extensions -> Modifications*** and click the refresh button under your profile (without re-loading this page in the browser). 3. Check the Tabby payment in the modification list. 4. Go to ***Extensions -> Extensions -> Modules*** in your OpenCart admin panel and activate Tabby Module. The example of Tabby Module settings: ![Tabby settings 1](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/opencart-settings-1.webp?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=09fa3de8115e584a8c8391bddf52d0cd) ![Tabby settings 2](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/opencart-settings-2.webp?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=7a3a9558e84c99fa2ebfd70b6c9b3689) #### Optional: Updating the OpenCart Extension When you want to update your current version with the new one. 1. Go to ***Extensions -> Modifications*** and delete the Tabby payment 2. Go to ***Extensions -> Installer*** and delete all files named oc3-tabby-x.x.x.ocmod.zip from install history 3. Upload and install the extension 4. Go to ***Extensions -> Modifications*** and click the refresh button under your profile (without reloading the page in the browser) 5. Check the Tabby payment in the modification list; 6. Go to ***Extensions -> Extensions -> Modules*** in your Opencart admin panel and activate Tabby Module 7. Open **Tabby Module** settings and click on Save changes button ### Configure Tabby as a Payment Method 1. Go to ***Extensions -> Extensions -> Modules*** in your OpenCart admin panel, find Tabby Module, click "Edit" and enter the **Public API Key** and **Secret API Key** for testing. 2. Go to ***Extensions -> Extensions -> Payments*** in your OpenCart admin panel and enable Tabby Installments (Split in 4). 3. Make sure the following settings are configured in any Tabby option Installments: – **Capture on – order\_placed** 4. Go to ***Design -> Layouts -> Checkout*** and add Tabby Module to Content Top **For Journal theme only:** Go to Journal → Layouts → Checkout, choose Top+, choose OpenCart (the last one) and choose Tabby Module →Tabby Checkout → Save changes. ### Test Your Integration After completing your integration, make an order and refund it to ensure that everything works as expected. **Coordinate Testing with Tabby** Before deploying Tabby integration to your production site, Tabby needs to validate the correctness of the integration. Contact your account manager to coordinate this test. ### Connect to live Tabby environment 1. Get your public and secret keys for the live environment. Ask you account manager to share the keys. 2. Set your live public and secret keys in the OpenCart admin panel, for this go to ***Extensions -> Extensions -> Modules -> Tabby Module*** in your OpenCart admin panel. 3. **Launch to Production**\ After you’ve connected to the live environment and we’ve tested your integration, you’re ready to deploy to your production environment and offer Tabby payment methods to your customers. ## Specific settings ### No Price snippet It is possible to turn on non standard Tabby snippets in ***Extensions -> Extensions -> Modules -> Tabby module (Tabby Checkout),*** parameter ***Tabby Promo theme*** should have **:non standard** specified: ![Tabby promo theme](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/opencart-non-standard.avif?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=9d8790a31b594fe6f793339f470d1871) Promotions theme ![No price snippet](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/no-price-0625.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=02d91eda914bb3fef38e64a6318b06ca) ![No price pop-up](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/no-price-pop-up-0625.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=62adc215065a2d2a93a83275f33faec6) No price snippet # OpenCart Product Catalogue Source: https://docs.tabby.sa/e-commerce-platforms/opencart/opencart-product-catalogue Enable the OpenCart Product Catalogue feature to list your store's products on the Tabby Shop. We've introduced an exciting new feature to the OpenCart plugin – the **Product Catalogue Feature**, designed to help you list your store’s products seamlessly on the Tabby Shop. The feature is enabled by default in new plugin versions starting from 2.9.0. Install or Update the plugin to the latest version via below link:\ [[https://storage.googleapis.com/plgns/opencart-latest.zip](https://storage.googleapis.com/plgns/opencart-latest.zip)](https://storage.googleapis.com/plgns/opencart-latest.zip) Check that the setting is enabled in the Tabby API options located at:\ **Extensions -> Tabby Module -> "Tabby Product Feed"** ![Tabby Product Feed](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/opencart-tabby-product-feed.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=4d0d0fa60844db1e669d5eb7c0297878) **Key features:** * **Automated Product Listing**: Easily list your entire OpenCart store inventory on the Tabby Shop without manual intervention. * **Real-Time Stock Updates**: Keep your product listings up-to-date effortlessly, as the plugin automatically syncs inventory availability and reflects out-of-stock items in real time. * **Increased Visibility**: Showcase your products to a broader audience by making them visible to all Tabby app users. * **Traffic Generation**: Drive interested shoppers directly to your OpenCart store, increasing clicks and potential sales. # Salesforce Source: https://docs.tabby.sa/e-commerce-platforms/salesforce Install and configure the Tabby cartridge for Salesforce Commerce Cloud. > [!NOTE] > > Tabby works on the Salesforce Commerce Cloud platform for all supported countries. ## Installation Steps *** *** *** *** *** Salesforce integration supposes adding the necessary cartridges into an SFCC reference application. Tabby cartridge package contains 3 folders: 1. int\_tabby\_core – Tabby core implementations both for Sitegenesis and SFRA 2. int\_tabby\_sfra – Tabby SFRA changes/implementations 3. bm\_tabby – Tabby Business Manager module > [!WARNING] > > Compatibility: SFRA v6.0.0 Compatibility Mode 21.7 ### Cartridges Installation #### Adding Cartridges in Salesforce Commerce Cloud Studio 1. Open package.json and ensure the base path mentioned is correctly resolved (please provide the correct path to app\_storefront\_base) 2. Open the command prompt and run ‘npm install’ (assuming node.js (12.21.0 and above) is installed) to install all of the local dependencies 3. Run the “npm run compile” 4. Create the dw\.json file: ``` { "hostname": "your-sandbox-hostname.demandware.net", "username": "yourlogin", "password": "yourpwd", "code-version": "version_to_upload_to" } ``` 5. Upload this code to the environment using the command ‘npm run uploadCartridge’, that will upload the **int\_tabby\_core** , **int\_tabby\_sfra** and **bm\_tabby** cartridges to the sandbox you specified in dw\.json file #### Activating Cartridges in Business Manager Before Tabby functionality can become available to Reference Architecture, the cartridges need to be added to the cartridge path of the site. In order to do this, follow the following instructions: * Log into Business Manager * Navigate to *Administration -> Sites -> Manage Sites* * Click on the site name * Go to the "Settings" tab * In the textbox cartridges add **int\_tabby\_sfra:int\_tabby\_core** before **app\_storefront\_base** > [!TIP] > > example: **int\_tabby\_sfra:int\_tabby\_core: app\_storefront\_base** * Click **Apply** * Repeat steps 3 to 5 for each site that is to use Tabby * To run the Jobs and have tabby BM module, Navigate to *Administration -> Sites -> Manage Sites* * Go to the "Manage the Business Manager site” tab * In the textbox cartridges add **int\_tabby\_core:bm\_tabby** to the start ### Configuration #### Importing Meta Data with a single Site Import All import files are located in the import folder (metadata) within the cartridge installation pack. To import necessary Tabby settings: * Open the metadata folder from the repository structure (open jobs.xml) and change the references of RefArchGlobal to your site id; * Open the metadata/sites folder from the repository structure and change the folder name from RefArchGlobal to your site id. If you have multiple sites with Tabby, then copy RefArchGlobal folder and rename it with your other site id(s). * Open the metadata/sites/YourSiteID/preferences.xml file from the repository structure; * Fill in **tabbyServiceCredentialPublicKey** and **tabbyServiceCredentialSecretKey** with keys provided by your account manager from Tabby; * Fill in **tabbyWebhooksTitle** and **tabbyWebhooksValue** values; * Compress the metadata folder from the repository structure; * Log in to the Business Manager and navigate to *Administration -> Site Development -> Site Import & Export*, upload the zipped folder, go back and use the import button to import the file; * After a successful import, the entire configuration requirements needed for Tabby are available according to your Tabby account data. #### Managing Tabby Custom Site Preferences In Business Manager, navigate to the *Site -> Merchant Tools -> Site Preferences -> Custom Preferences*. A custom site preference group "Tabby" is available. Please select it and edit the attributes according to your Tabby account data. | | | | | - | - | - | | **Site Preference** | **Description** | **Values** | | Is Tabby Enable | This field represents the enable/disable tabby implementation. | Boolean: True/FalseDefault: True | | Tabby Environment | This field represents the type of the environment. | Enum of String: Development -> Development instancesProduction -> Production instancesDefault: Development | | Service Credential Public Key | This field represents the tabby service credential public key (pk\_...). | String: (Mandatory) The public API key that Tabby provided to you | | Service Credential Secret Key | This field represents the tabby service credential secret key (sk\_...). | String: (Mandatory) The secret API key that Tabby provided to you | | Is Tabby Installments Enable | This field represents the Installments payment method is enabled. | Boolean: True/False Default: True | | Is Tabby Credit Card Installments Enable(Deprecated) | This field represents the Credit Card Installments payment method is enabled. | Boolean: True/False Default: False | | Is Tabby Pay Later Enable | This field represents the Pay Later payment method is enabled. | Boolean: True/False Default: False | | Immediate Capture On Authorize | This field represents if the Immediate Capture on Authorize is enabled. If Yes -> Capture request will be triggered automatically after success Authorization. If No -> Tabby-Capture scheduled job will send the capture request. | Boolean: True/False Default: True | | Installments Count | This field represents Installments counts. | Integer:Default: 4 | | Tabby Merchant Code Per Country | This field represents the Merchant codes per country in a JSON format: \{ "AE": "uae","SA": "ksa","KW": "kwt"} | Text (JSON) \{ "AE": "uae","SA": "ksa","KW": "kwt"} - need to define Site countries only. | | Card Size | This field represents the tabby card size, can be narrow or wide, depending on the width | Enum of String: Narrow/Wide Default: Narrow | | Card Theme | This field represents the tabby card theme, can be default or black, depending on the styles | Enum of String: Default/Black Default: Default | | Webhooks Title | This field represents the arbitrary header name to sign the request for the webhooks.Will be used to verify webhook requests that tabby system sends. | String: | | Webhooks Value | This field represents the random string to sign the request for the webhooks.Will be used to verify webhook requests that tabby system sends. | String: | #### Managing Tabby Payment Processor In Business Manager select the required site from the dropdown and navigate to *Merchant Tools -> Ordering -> Payment Processors*. #### Managing Tabby Payment Methods In the integration package, a payment method definition is provided in the file that you have already imported 1. Navigate to *Merchant Tools -> Ordering -> Payment Methods*; 2. Enable/Disable the Payment Method under *Ordering -> Payment Methods* section that needs to be activated. Repeat these steps for all the Payment Methods that are required to be supported on the site. #### Managing Jobs In the integration package, a job definition is provided in the import zip (metadata.zip) file (jobs.xml). Make sure to associate the jobs with the respective merchant site. *Tabby-CaptureShippedOrders* It captures the payments that are authorized by Tabby. This job fetches the orders which have confirmationStatus=CONFIRMED, paymentStatus=NOTPAID, shippingStatus=SHIPPED and custom.tabbyPaymentStatus: AUTHORIZED. > [!TIP] > > Script file: int\_tabby\_core/cartridge/scripts/jobs/capture.js If the site preference “**Immediate Capture On Authorize**” is set to "True", it is not necessary to enable this job. *Tabby-RegisterWebhooks* It registers the Webhooks for the sites defined by merchant*codes from the "Site preference" tab. Each* merchant\_code needs to be registered separately. This job is a one time job to register the Webhooks. It is important to fill in “Webhooks Title” and “Webhooks Value” in the "Site Preferences" before running this job. > [!TIP] > > Script file int\_tabby\_core/cartridge/scripts/jobs/webhooks.js *Tabby-Feedback* It processes the TabbyFeedback custom objects that are saved within the TabbyWebhooks-Process endpoint. Custom object details are to compare with the order payment statuses. If there is a difference between the order/payment status, the job will synchronize the order/payment statuses based on the TabbyFeedback details. > [!TIP] > > Script file int\_tabby\_core/cartridge/scripts/jobs/feedback.js #### Tabby Business Manager Module The cartridge for Business Manager module bm\_tabby is present in the installation package. It helps processing capture and refund APIs. To enable/activate it, do the following: 1. Navigate to *Administration -> Organization -> Roles & Permissions*; 2. Click **Administrator** in the Roles; 3. Click **Business Manager Modules**; 4. Select the sites where you have Tabby; 5. Click **Apply**; 6. Scroll to *Ordering -> Tabby Orders* and check the checkbox; 7. Click **Update** at the bottom of the page; Once it is enabled, “Tabby Orders” will be placed under *Merchant Tools > Ordering* # Salla Source: https://docs.tabby.sa/e-commerce-platforms/salla > [!NOTE] > > Tabby works on the Salla platform for the KSA only with SAR as currency. 1. [Register with Tabby](https://merchant.tabby.ai/) (KSA: [merchant.tabby.sa](https://merchant.tabby.sa/)) and fill all required business details to collect Tabby API Keys. 2. Fill in the keys using the instruction below: ![Tabby & Salla](https://mintcdn.com/tabby-26277sa/hJvAwtn7TTitdYQj/images/salla-1-upd.png?fit=max\&auto=format\&n=hJvAwtn7TTitdYQj\&q=85\&s=d823090a8d1a4b211f5a8a0840c973a7) ![Step 1](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/salla-2.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=7808ec3389dae27956d780f090cf1908) ![Step 2](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/salla-3.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=8087adffa2f94d2121b4bfc57c89b6c9) ![Step 3](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/salla-4.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=29aaea74f26218c4f14918f6b7c51d01) ![Step 4](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/salla-5.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=b923372eb29cdc66caa8a8b815aeec1c) ![Step 5](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/salla-6.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=2f89ef74a73da1939578ea91ae0efcb0) ![Contact information](https://mintcdn.com/tabby-26277sa/4QuRvEaK4cqRahj2/images/salla-7.png?fit=max\&auto=format\&n=4QuRvEaK4cqRahj2\&q=85\&s=75d10856e2e4d9fb88baa9b88223e5fa) # Shopify Plugin Installation Source: https://docs.tabby.sa/e-commerce-platforms/shopify/shopify-plugin-installation Install the Tabby app for Shopify, configure webhooks and settings, and go live. > [!NOTE] > > Tabby works on the Shopify platform for all supported countries. Currency conversion may apply for multi-currency stores, check **Multi-Currency Processing and Currency Conversion** below. ## Installation Steps No time to read or have questions on any of the steps? Watch video guide on\ **How to set up Tabby on Shopify:**