# Documentation (/docs) Kashier lets you accept payments in Egypt through a simple API, a hosted checkout, ready-made store plugins, and in-person terminals. Everything can be tested for free in the test environment before you go live. ## Which integration should I use? [#which-integration-should-i-use] | Integration path | When to use it | Time to launch | Technical level | | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------- | | [Payment sessions](/docs/accept-payments/payment-sessions) / [embedded checkout](/docs/accept-payments/payment-sessions#step-2-send-the-customer-to-pay) | A checkout page hosted by Kashier, or embedded in your own page, reached by a single backend API call | Hours to days — needs a backend call to create a payment session | Low | | [Direct API integration](/docs/direct-api) | Full control over your own card form, saved-card tokens, 3D Secure, wallets, and installments | Days — the most integration work of any path here | High — also changes your PCI DSS scope | | [E-commerce plugins](/docs/plugins) | You run WooCommerce, Shopify, Magento, PrestaShop, or another supported store platform and want no custom code | Fastest — install and configure a pre-built plugin | None to low | | [In-person payments (POS)](/docs/pos) | You want to drive Kashier POS terminals from your own Android app or backend | Requires physical terminals plus setup (IP whitelisting, APK signing for app-to-app) | Medium to high | Need something in between? [Payment links](/docs/accept-payments/payment-links) let you accept a payment with no code at all, and [recurring payments](/docs/accept-payments/recurring) charge customers on a schedule. ## Choose your integration path [#choose-your-integration-path] ## Environments [#environments] Kashier has separate test and live environments. Use the test environment with test API keys while you build — no real money moves. See [API keys](/docs/get-started/api-keys) for how the two modes work. # Accounts and payout methods (/docs/account-and-balance/accounts) Every merchant starts with one **primary account**. Payout-destination details — bank name, account number, account holder name, and so on — are not a separate "beneficiary" object anywhere in the API. They're simply fields on an **Account's `payoutMethod`**. If you're looking for a beneficiary resource to create or manage, this is it: manage the `payoutMethod` on an account instead. You can also create **additional accounts** beyond the primary one, each with its own independent `payoutMethod`. This page covers creating and managing accounts and their payout methods; for the immutable balance ledger and holds, see [Balance ledger and holds](/docs/account-and-balance/balance). `GET`/`PUT /v2/account/payoutMethod` always operate on your **primary** account specifically. To manage an additional (non-primary) account's payout method — or any other field on any account — use `GET`/`PUT /v2/account/:accountId` with that account's `accountId` instead. There is no endpoint to delete an account or a payout method. Once created, an account persists; you can edit its `payoutMethod` and other fields, but not remove the account itself. ## Create an additional account [#create-an-additional-account] Creates a new, non-primary account for your merchant. There's no cap on the number of accounts you can create today — don't assume there's a limit when designing your integration. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/account](https://test-api.kashier.io/v2/account) | | LIVE-URL | [https://api.kashier.io/v2/account](https://api.kashier.io/v2/account) | | Method | POST | ### Body parameters [#body-parameters] | Key | Description | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | accountName | Name for the new account. | | payoutMethod | Object describing the payout destination. No field inside it is required — you can create the account first and set its payout method later via [Edit an account](#edit-an-account). | | payoutMethod.method | Identifies the payout rail, e.g. a bank transfer vs. a mobile wallet. | | payoutMethod.accountHolderName | Name on the payout destination. Accepts Arabic and Latin letters, digits, spaces, periods, hyphens, and apostrophes; max 70 characters. | | payoutMethod.accountNumber | Bank account number or wallet number. | | payoutMethod.bankName | Bank name. | | payoutMethod.bankAbbreviation | Bank abbreviation/code. | | payoutMethod.bankBranchName | Bank branch name. | | payoutMethod.bankBranchCode | Bank branch code. | ```bash curl --location 'https://test-api.kashier.io/v2/account' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --data '{ "accountName": "Second Store Account", "payoutMethod": { "method": "bank", "accountHolderName": "Ahmed Hassan", "accountNumber": "1234567890", "bankName": "National Bank of Egypt", "bankAbbreviation": "NBE", "bankBranchName": "Downtown", "bankBranchCode": "001" } }' ``` ### Headers [#headers] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response] ```json { "accountId": "ACC-46254-582-02", "accountName": "Second Store Account", "merchantId": "MID-46254-582", "isPrimary": false, "totalBalance": 0, "availableBalance": 0, "onHoldBalance": 0, "payoutFees": 5, "isIncludeInBulkTransfer": true, "payoutMethod": { "method": "bank", "accountHolderName": "Ahmed Hassan", "accountNumber": "1234567890", "bankName": "National Bank of Egypt", "bankAbbreviation": "NBE", "bankBranchName": "Downtown", "bankBranchCode": "001" }, "createdAt": "2026-06-18T10:30:00.000Z", "updatedAt": "2026-06-18T10:30:00.000Z" } ``` | Field | Description | | ----------------------------------------------------- | ------------------------------------------------------------------------------- | | `accountId` | Unique identifier for the new account. | | `accountName` | The account's name. | | `merchantId` | The merchant the account belongs to. | | `isPrimary` | Always `false` for an account created through this endpoint. | | `totalBalance` / `availableBalance` / `onHoldBalance` | Running balances for the new account — all `0` until it starts receiving funds. | | `payoutFees` | Flat fee applied per payout for this account. | | `isIncludeInBulkTransfer` | Whether the account is eligible for the recurring payout run. | | `payoutMethod` | The payout destination you supplied, echoed back. | | `createdAt` / `updatedAt` | Timestamps. | ## Get account info [#get-account-info] Fetch your **primary** account — a GET request to `/v2/account`, with no account ID needed. To fetch a non-primary account, or to look up the primary account by its `accountId`, use [Get an account](#get-an-account) below instead. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/account](https://test-api.kashier.io/v2/account) | | LIVE-URL | [https://api.kashier.io/v2/account](https://api.kashier.io/v2/account) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/account' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-1] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-1] Full parameter and response reference → [Get account info](/docs/api-reference/payouts/getAccountInfo). ## Get an account [#get-an-account] Fetch a single account by ID, including its `payoutMethod`. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/account/:accountId](https://test-api.kashier.io/v2/account/:accountId) | | LIVE-URL | [https://api.kashier.io/v2/account/:accountId](https://api.kashier.io/v2/account/:accountId) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/account/:accountId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-2] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-2] ```json { "accountId": "ACC-46254-582-02", "accountName": "Second Store Account", "merchantId": "MID-46254-582", "isPrimary": false, "totalBalance": 12500, "availableBalance": 12000, "onHoldBalance": 500, "allowedNegativeBalance": 0, "payoutFees": 5, "isIncludeInBulkTransfer": true, "payoutMethod": { "method": "bank", "accountHolderName": "Ahmed Hassan", "accountNumber": "1234567890", "bankName": "National Bank of Egypt", "bankAbbreviation": "NBE", "bankBranchName": "Downtown", "bankBranchCode": "001" }, "createdAt": "2026-06-18T10:30:00.000Z", "updatedAt": "2026-06-20T09:15:00.000Z" } ``` Fields match the create response above, plus `allowedNegativeBalance` (how far the account may go negative before further deductions are blocked). ## Edit an account [#edit-an-account] Update an account's name and/or payout method. This endpoint requires OTP verification. Call it without an `x-otp` header first to trigger the code, then retry the same request with `x-otp` set to the code you received. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/account/:accountId](https://test-api.kashier.io/v2/account/:accountId) | | LIVE-URL | [https://api.kashier.io/v2/account/:accountId](https://api.kashier.io/v2/account/:accountId) | | Method | PUT | ### Body parameters [#body-parameters-1] | Key | Description | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | accountName | New name for the account. | | payoutMethod | New payout method for the account — same shape as [Create an additional account](#create-an-additional-account). Sending `payoutMethod` **replaces it wholesale**; it isn't merged field by field. | ```bash curl --location --request PUT 'https://test-api.kashier.io/v2/account/:accountId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --header 'x-otp: 123456' \ --data '{ "accountName": "Second Store Account (renamed)", "payoutMethod": { "accountHolderName": "Ahmed Hassan", "accountNumber": "9876543210", "bankName": "National Bank of Egypt", "bankAbbreviation": "NBE", "bankBranchName": "Maadi", "bankBranchCode": "014" } }' ``` ### Headers [#headers-3] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | | x-otp | The OTP code, once you've received one. Omit it on the first call to trigger generation. | ### Response [#response-3] Returns the updated account in the same shape as [Get an account](#get-an-account). ## Get the primary account's payout method [#get-the-primary-accounts-payout-method] Returns the `payoutMethod` for your **primary** account only. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/account/payoutMethod](https://test-api.kashier.io/v2/account/payoutMethod) | | LIVE-URL | [https://api.kashier.io/v2/account/payoutMethod](https://api.kashier.io/v2/account/payoutMethod) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/account/payoutMethod' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-4] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-4] ```json { "method": "bank", "accountHolderName": "Ahmed Hassan", "accountNumber": "1234567890", "bankName": "National Bank of Egypt", "bankAbbreviation": "NBE", "bankBranchName": "Downtown", "bankBranchCode": "001" } ``` ## Update the primary account's payout method [#update-the-primary-accounts-payout-method] Replaces the `payoutMethod` on your **primary** account. The request body **is** the payout method object — there's no wrapper. This endpoint requires OTP verification, the same way [Edit an account](#edit-an-account) does: call it without `x-otp` to trigger a code, then retry with `x-otp` set. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/account/payoutMethod](https://test-api.kashier.io/v2/account/payoutMethod) | | LIVE-URL | [https://api.kashier.io/v2/account/payoutMethod](https://api.kashier.io/v2/account/payoutMethod) | | Method | PUT | ### Body parameters [#body-parameters-2] | Key | Description | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | method | Identifies the payout rail, e.g. a bank transfer vs. a mobile wallet. | | accountHolderName | Name on the payout destination. Accepts Arabic and Latin letters, digits, spaces, periods, hyphens, and apostrophes; max 70 characters. | | accountNumber | Bank account number or wallet number. | | bankName | Bank name. | | bankAbbreviation | Bank abbreviation/code. | | bankBranchName | Bank branch name. | | bankBranchCode | Bank branch code. | No field is required — this is the same shared payout-method shape used across account creation and editing, and it's replaced wholesale on every update, so send the full object. ```bash curl --location --request PUT 'https://test-api.kashier.io/v2/account/payoutMethod' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --header 'x-otp: 123456' \ --data '{ "method": "bank", "accountHolderName": "Ahmed Hassan", "accountNumber": "1234567890", "bankName": "National Bank of Egypt", "bankAbbreviation": "NBE", "bankBranchName": "Downtown", "bankBranchCode": "001" }' ``` This endpoint replaces your primary account's payout method wholesale, and Kashier's internal sources currently disagree on the exact field names it expects. Rather than offer a Send button that could overwrite a working payout destination with a half-right body, this page documents the call only. Build the request from the cURL above and confirm the field names against a `GET /v2/account/payoutMethod` response for your own account first. ### Headers [#headers-5] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | | x-otp | The OTP code, once you've received one. Omit it on the first call to trigger generation. | ### Response [#response-5] Returns the updated `payoutMethod` in the same shape as [Get the primary account's payout method](#get-the-primary-accounts-payout-method). ## Get account records [#get-account-records] Returns a paginated feed of account-level activity for a given account — broader than the balance ledger (see [Balance ledger and holds](/docs/account-and-balance/balance)), since it isn't limited to balance-affecting events. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/account/:accountId/records](https://test-api.kashier.io/v2/account/:accountId/records) | | LIVE-URL | [https://api.kashier.io/v2/account/:accountId/records](https://api.kashier.io/v2/account/:accountId/records) | | Method | GET | ### Query parameters [#query-parameters] | Key | Description | | ----- | ---------------------------------------- | | page | Page number for pagination. Example: `1` | | limit | Records per page. Example: `10` | ```bash curl --location 'https://test-api.kashier.io/v2/account/:accountId/records?page=1&limit=10' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-6] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-6] ```json { "data": [ { "...": "one entry per account-level activity item" } ], "total": 12, "page": 1, "limit": 10 } ``` `data` is paginated the same way as the balance ledger (`total`/`page`/`limit`). The shape of an individual entry depends on the kind of activity it represents; use the "Try it" panel above against a test account to inspect a live response. ## Export account records [#export-account-records] The same account activity feed as a file download. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/account/:accountId/records/export](https://test-api.kashier.io/v2/account/:accountId/records/export) | | LIVE-URL | [https://api.kashier.io/v2/account/:accountId/records/export](https://api.kashier.io/v2/account/:accountId/records/export) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/account/:accountId/records/export' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --output account-records.xlsx ``` ### Headers [#headers-7] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ## Account overview [#account-overview] Returns an overview for a single account. Merchant-only (not callable by agents). | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/account/overview/:accountId](https://test-api.kashier.io/v2/account/overview/:accountId) | | LIVE-URL | [https://api.kashier.io/v2/account/overview/:accountId](https://api.kashier.io/v2/account/overview/:accountId) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/account/overview/:accountId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-8] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-7] Returns a `200` with a JSON object summarizing the given account. The exact field set isn't confirmed here — use the "Try it" panel above against a test account to inspect a live response. ## List accounts overview [#list-accounts-overview] Returns an overview covering all of your accounts at once — useful for a dashboard-style summary instead of fetching each account individually. Merchant-only (not callable by agents). | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/account/overview/accounts-list](https://test-api.kashier.io/v2/account/overview/accounts-list) | | LIVE-URL | [https://api.kashier.io/v2/account/overview/accounts-list](https://api.kashier.io/v2/account/overview/accounts-list) | | Method | GET | ### Query parameters [#query-parameters-1] | Key | Description | | ----- | ---------------------------------------- | | page | Page number for pagination. Example: `1` | | limit | Accounts per page. Example: `10` | ```bash curl --location 'https://test-api.kashier.io/v2/account/overview/accounts-list?page=1&limit=10' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-9] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-8] Returns a `200` with a paginated JSON object — `page`/`limit` mirror the query parameters above, the same pagination envelope used elsewhere in this API. The exact per-account field set isn't confirmed here — use the "Try it" panel above against a test account to inspect a live response. ## Payments overview [#payments-overview] Returns a payments summary for a given account over a date range. Merchant-only (not callable by agents). | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/account/overview/payments/:accountId](https://test-api.kashier.io/v2/account/overview/payments/:accountId) | | LIVE-URL | [https://api.kashier.io/v2/account/overview/payments/:accountId](https://api.kashier.io/v2/account/overview/payments/:accountId) | | Method | GET | ### Query parameters [#query-parameters-2] | Key | Description | | -------- | --------------------------------------------- | | dateFrom | Start of the summary window, as `YYYY-MM-DD`. | | dateTo | End of the summary window, as `YYYY-MM-DD`. | | page | Page number for pagination. Example: `1` | | limit | Items per page. Example: `20` | The window below is deliberately wide so it covers whatever history your account already has — narrow it to the range you actually want to summarize. ```bash curl --location 'https://test-api.kashier.io/v2/account/overview/payments/:accountId?dateFrom=2020-01-01&dateTo=2030-12-31' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-10] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-9] Returns a `200` with a JSON object summarizing payments on the account over the requested date range. The exact field set isn't confirmed here — use the "Try it" panel above against a test account to inspect a live response. ## Payouts overview [#payouts-overview] Returns a payouts summary for a given account over a date range. Merchant-only (not callable by agents). | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/account/overview/payouts/:accountId](https://test-api.kashier.io/v2/account/overview/payouts/:accountId) | | LIVE-URL | [https://api.kashier.io/v2/account/overview/payouts/:accountId](https://api.kashier.io/v2/account/overview/payouts/:accountId) | | Method | GET | ### Query parameters [#query-parameters-3] | Key | Description | | -------- | --------------------------------------------- | | dateFrom | Start of the summary window, as `YYYY-MM-DD`. | | dateTo | End of the summary window, as `YYYY-MM-DD`. | | page | Page number for pagination. Example: `1` | | limit | Items per page. Example: `20` | As with the payments overview, the window below is deliberately wide — narrow it to the range you actually want. ```bash curl --location 'https://test-api.kashier.io/v2/account/overview/payouts/:accountId?dateFrom=2020-01-01&dateTo=2030-12-31' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-11] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-10] Returns a `200` with a JSON object summarizing payouts on the account over the requested date range. The exact field set isn't confirmed here — use the "Try it" panel above against a test account to inspect a live response. # Balance ledger and holds (/docs/account-and-balance/balance) Kashier keeps an immutable ledger of every balance-affecting event on your account — settlements, payouts, refunds, and adjustments each post a record. Use the balance ledger to reconcile your Kashier balance against your own books, and use the holds endpoint to see why your available balance can be lower than your total balance. This page covers the balance ledger under `/v2/balance/*` — immutable balance-affecting records (settlements, payouts, adjustments) plus holds. A separate, broader account activity feed also exists — see [Get account records](/docs/account-and-balance/accounts#get-account-records) on the [Accounts and payout methods](/docs/account-and-balance/accounts) page. Don't confuse the two — they return different record shapes for different purposes. ## Get balance records (the ledger) [#get-balance-records-the-ledger] Returns a paginated list of balance-affecting records for an account — the core ledger. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/balance/records/:accountId](https://test-api.kashier.io/v2/balance/records/:accountId) | | LIVE-URL | [https://api.kashier.io/v2/balance/records/:accountId](https://api.kashier.io/v2/balance/records/:accountId) | | Method | GET | ### Query parameters [#query-parameters] | Key | Description | | -------- | --------------------------------------------------------- | | page | Page number for pagination. Example: `1` | | limit | Records per page. Example: `10` | | dateFrom | Start date for a date-range filter. Example: `2026-01-01` | | dateTo | End date for a date-range filter. Example: `2026-06-30` | ```bash curl --location 'https://test-api.kashier.io/v2/balance/records/:accountId?page=1&limit=10' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response] ```json { "data": [ { "accountId": "ACC-39550-436-01", "amount": 500, "originalAmount": 500, "fees": 0, "operation": "manualAdjustment", "origin": "operations team", "originReference": "REF-12345", "comment": "Manual adjustment", "valueDate": "2026-06-18", "isReflected": true, "totalBalanceBefore": 12000, "totalBalanceAfter": 12500, "createdAt": "2026-06-18T15:30:00Z" }, { "accountId": "ACC-39550-436-01", "amount": 1000, "originalAmount": 1015, "fees": 15, "operation": "settlement", "origin": "settlements", "originReference": "STW-88213", "comment": "Settlement", "valueDate": "2026-06-18", "isReflected": true, "totalBalanceBefore": 11000, "totalBalanceAfter": 12000, "createdAt": "2026-06-18T14:00:00Z" } ], "total": 2, "page": 1, "limit": 10 } ``` | Field | Description | | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data[].accountId` | The account the record was posted against (`ACC-` format). | | `data[].amount` | Net amount after fees — this is what moves the balance. | | `data[].originalAmount` | Gross amount before fees. | | `data[].fees` | Fees deducted from `originalAmount`. | | `data[].operation` | What generated the record. Free text; values in use include `refund`, `settlement`, `payout`, `deduct`, `transfer` and `manualAdjustment`. | | `data[].origin` | The service that posted it, e.g. `settlements`, `transfers`, `instant settlement`. | | `data[].originReference` | The originating reference (`TRX-`, `STW-`, `STB-`, `ISR-`, `TRS-`…). Together with `accountId`, `operation` and `origin` this forms the idempotency key. | | `data[].comment` | Free-text description of what generated the record. | | `data[].valueDate` | The date the record is value-dated against. | | `data[].isReflected` | `false` while the entry is future-dated and not yet applied to the balance. | | `data[].totalBalanceBefore` / `totalBalanceAfter` | Balance snapshots either side of the entry. | | `data[].createdAt` | When the record was created. | | `total` | Total number of records matching the filter. | | `page` | Current page number. | | `limit` | Records per page. | ## Get balance record details [#get-balance-record-details] Fetch the full detail of a single ledger record by its ID — use the `id` returned from the list above. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/balance/record-details/:recordId](https://test-api.kashier.io/v2/balance/record-details/:recordId) | | LIVE-URL | [https://api.kashier.io/v2/balance/record-details/:recordId](https://api.kashier.io/v2/balance/record-details/:recordId) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/balance/record-details/:recordId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-1] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-1] ```json { "accountId": "ACC-39550-436-01", "amount": 500, "originalAmount": 500, "fees": 0, "operation": "manualAdjustment", "origin": "operations team", "originReference": "REF-12345", "comment": "Manual adjustment", "valueDate": "2026-06-18", "totalBalanceBefore": 12000, "totalBalanceAfter": 12500, "metaData": { "fees": 0, "vat": 0 }, "createdAt": "2026-06-18T15:30:00Z" } ``` Fields match the ledger list above, plus `metaData`, which carries the fee/VAT breakdown for the record. ## Get payout details [#get-payout-details] Given a ledger record that represents a payout, list the underlying transactions that were bundled into it. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/balance/payout-details/:recordId](https://test-api.kashier.io/v2/balance/payout-details/:recordId) | | LIVE-URL | [https://api.kashier.io/v2/balance/payout-details/:recordId](https://api.kashier.io/v2/balance/payout-details/:recordId) | | Method | GET | ### Query parameters [#query-parameters-1] | Key | Description | | --------- | --------------------------------------------------------- | | page | Page number for pagination. Example: `1` | | limit | Records per page. Example: `10` | | sortBy | Field to sort by. Example: `transactionDate` | | sortOrder | Sort order, `asc` or `desc`. Example: `desc` | | status | Filter transactions by status. Example: `completed` | | dateFrom | Start date for a date-range filter. Example: `2026-01-01` | | dateTo | End date for a date-range filter. Example: `2026-06-30` | ```bash curl --location 'https://test-api.kashier.io/v2/balance/payout-details/:recordId?page=1&limit=10' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-2] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-2] ```json { "recordId": "507f1f77bcf86cd799439029", "accountId": "507f1f77bcf86cd799439012", "amount": 5000, "status": "processed", "payoutDate": "2026-06-18", "transactions": [ { "transactionId": "txn_001", "amount": 1000, "status": "settled", "settleDate": "2026-06-18" } ], "totalTransactions": 5 } ``` ### Export payout details [#export-payout-details] The same underlying data is also available as a file download. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/balance/payout-details/:recordId/export](https://test-api.kashier.io/v2/balance/payout-details/:recordId/export) | | LIVE-URL | [https://api.kashier.io/v2/balance/payout-details/:recordId/export](https://api.kashier.io/v2/balance/payout-details/:recordId/export) | | Method | GET | | Key | Description | | -------- | --------------------------------------------------------- | | status | Filter transactions by status. Example: `completed` | | dateFrom | Start date for a date-range filter. Example: `2026-01-01` | | dateTo | End date for a date-range filter. Example: `2026-06-30` | ```bash curl --location 'https://test-api.kashier.io/v2/balance/payout-details/:recordId/export' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --output payout-details.xlsx ``` ## Get account holds [#get-account-holds] Returns holds placed on an account. Holds reduce your **available** balance without changing your **total** balance — this is why the amount you can withdraw or use can be less than your account's total balance. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/hold/account/:accountId](https://test-api.kashier.io/v2/hold/account/:accountId) | | LIVE-URL | [https://api.kashier.io/v2/hold/account/:accountId](https://api.kashier.io/v2/hold/account/:accountId) | | Method | GET | ### Query parameters [#query-parameters-2] | Key | Description | | ------ | ------------------------------------------------------- | | page | Page number for pagination. Example: `1` | | limit | Records per page. Example: `10` | | status | Filter by hold status: `HELD`, `RELEASED` or `DEDUCTED` | ```bash curl --location 'https://test-api.kashier.io/v2/hold/account/:accountId?page=1&limit=10' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-3] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-3] ```json { "data": [ { "holdId": "HLD-39550-436-0007", "accountId": "ACC-39550-436-01", "amount": 1000, "status": "HELD", "valueDate": "2026-06-18", "createdAt": "2026-06-18T10:30:00Z" } ], "total": 1, "page": 1, "limit": 10 } ``` | Field | Description | | ------------------ | ------------------------------------------------------- | | `data[].holdId` | Unique identifier for the hold (`HLD-` format). | | `data[].accountId` | The account the hold is placed against (`ACC-` format). | | `data[].amount` | The amount held. | | `data[].status` | Hold lifecycle state: `HELD`, `RELEASED` or `DEDUCTED`. | | `data[].valueDate` | The date the hold is value-dated against. | | `data[].createdAt` | When the hold was created. | The `status` filter accepts the hold-lifecycle values `HELD`, `RELEASED` and `DEDUCTED`. A hold starts `HELD`; releasing it returns the amount to the available balance, deducting it removes the amount for good. # Accounts and balance (/docs/account-and-balance) Every merchant has a balance and at least one account. This section covers the two resources behind them: **accounts**, which hold your payout methods and can be created beyond the primary one, and the **balance ledger**, an immutable record of every balance-affecting event on your account. # API reference and playground (/docs/api-reference) Save your test keys once. Every endpoint page then fills its own auth fields and request body, so you can press **Send** and read the live response. ## Check your setup [#check-your-setup] ## Endpoints [#endpoints] ## Good to know [#good-to-know] * Requests go straight from your browser to Kashier — the documented URLs point at the test hosts, and your keys never reach this site's servers. * Keys stay in your browser's localStorage and travel only with requests you fire. * Each endpoint renders your request as cURL, JavaScript, Python, PHP and more. * Everything here is generated from the [OpenAPI spec](/openapi.yaml) — point your tooling or AI agent at it. * New to the API? The [quick start](/docs/get-started/quickstart) walks one payment end to end. # Apple Pay (/docs/accept-payments/apple-pay) Add an Apple Pay button to your website with the Kashier SDK. Customers on Apple devices pay directly from your checkout page. An Apple Pay payment settles over the card rail (MPGS), so refunds, void and authorize/capture behave the same as a card payment. `enable3DS` is forced to `false` — the device cryptogram already carries the authentication, so no separate 3D Secure step runs. Google Pay is not supported. ## Prerequisites [#prerequisites] Before implementing the Kashier Apple Pay integration, ensure you have: 1. A website with HTTPS enabled 2. Provided your website domain to Kashier's team so it can be verified with Apple 3. The Kashier SDK script ## Step 1: Domain registration [#step-1-domain-registration] Provide your website domain to the Kashier support team for certification with Apple. ## Step 2: Certificate installation [#step-2-certificate-installation] After receiving the Apple Pay certificate from Kashier: 1. Save the certificate file as `apple-developer-merchantid-domain-association.txt` 2. Create a `.well-known` directory in your web server's root directory 3. Upload the certificate file to this directory 4. Ensure the file is accessible at: `https://your-domain.com/.well-known/apple-developer-merchantid-domain-association.txt` ## Implementation [#implementation] After receiving the Apple Pay certificate from Kashier: ### Basic integration [#basic-integration] Add the following code to your checkout page: ```html
``` ### HTML elements [#html-elements] | Element ID | Required | Description | | ------------------ | -------- | ---------------------------------------------------------------- | | kashier-sdk-id | Yes | Container where the Apple Pay button will be rendered | | kashier-success-id | No | Optional container for displaying successful payment information | | kashier-failure-id | No | Optional container for displaying payment failure information | ## Configuration options [#configuration-options] The `kashierSDK.load()` method accepts a configuration object with the following properties: ### Required parameters [#required-parameters] | Parameter | Type | Description | | -------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | data.mode | String | Environment mode: 'test' or 'live' | | data.sessionId | String | Unique payment session identifier provided by Kashier's payment session creation API. See [payment sessions](/docs/accept-payments/payment-sessions). | ### Optional parameters [#optional-parameters] | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------- | | locale | String | Language for the Apple Pay button: `'en'` (English) or `'ar'` (Arabic) | | onSuccess | Function | Callback function that executes after successful payment completion | | onFailure | Function | Callback function that executes after payment failure | ## Support [#support] For technical support, contact Kashier support at: * Email: [techsupport@kashier.io](mailto:techsupport@kashier.io) # Authorize and capture (/docs/accept-payments/authorize-capture) You can use an authorized transaction to hold the amount of the order or service. You can either manually capture a partial or full amount of the order payment. Order amounts can also be released back to customers. ## How it works [#how-it-works] * You will be notified when the order has been authorized via your dashboard, and in the case of [Payment Sessions](/docs/accept-payments/payment-sessions), the Status response will be AUTHORIZED, and the webhook event will be "authorize." * You can use the manual release or manual capture operations in the endpoint. Approval is needed. The Authorization Capture feature can be enabled by contacting your account manager or customer success. ## Capture [#capture] The amount of the order is either captured fully or partially. Capture is done by sending a PUT request with the order ID `orderId` in the URL as a parameter, and inserting a transaction ID and amount in the body. In case you are still in the development phase, you will need to call our API using the following testing endpoint API URL: | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------ | | URL | [https://test-fep.kashier.io/v3/orders/:orderId](https://test-fep.kashier.io/v3/orders/:orderId) | | Method | PUT | Meanwhile, whenever you are ready for production, you should use the following production API endpoint URL instead: | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------- | | URL | [https://fep.kashier.io/v3/orders/:orderId](https://fep.kashier.io/v3/orders/:orderId) | | Method | PUT | ### Headers [#headers] | Key | Description | | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Capture, void and refund authenticate with the `Authorization` **secret key** — the same credential the [Dashboard API](/docs/dashboard-api/authentication) uses. That differs from the pay call on the same host: creating a payment (`POST /v3/orders`) is gated by a [`Kashier-Hash`](/docs/direct-api/hashing) header and takes no secret key. Kashier publishes no `Kashier-Hash` string-to-sign for `PUT /v3/orders/:orderId`, so there is nothing to compute here. Sending the header anyway is what some older integrations do and we have no report of it being rejected, but that is unconfirmed — if an update ever fails with `INVALID_HASH_CHECK`, raise it with Kashier support. ```bash curl -X 'PUT' 'https://test-fep.kashier.io/v3/orders/:orderId' -H 'Authorization: your_secretKey' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"apiOperation":"CAPTURE","transaction":{"amount":3}}' ``` ### Body structure [#body-structure] ```json { "apiOperation": "CAPTURE", "transaction": { "amount": 3 } } ``` Full parameter and response reference → [Refund, void, or capture an order](/docs/api-reference/order-operations/updateOrder). Capture an authorized amount as soon as you are ready to take the money. An authorization is a hold placed by the issuing bank, and holds do not last indefinitely — how long yours stays capturable depends on the issuer and on your account configuration, so confirm the window that applies to you with your Kashier account manager rather than assuming one. ## Release [#release] Releasing an authorized amount is a **void** of the authorize transaction — there is no separate release operation. Send a PUT request to the same endpoint with `apiOperation` set to `VOID`, the order ID `orderId` in the URL, and the `transactionId` of the authorized transaction as `transaction.targetTransactionId` in the body. Include `transaction.amount` to release only part of the hold. The `transactionId` is returned in the callback URL, webhook, and response body. ```json { "apiOperation": "VOID", "transaction": { "targetTransactionId": "TX-1902526801" } } ``` Kashier also releases the remainder of a hold for you automatically after a partial capture when you send `autoVoid: true` on the capture. Full contract → [Void](/docs/accept-payments/void) # Connected accounts (/docs/accept-payments/connected-accounts) This feature gives the ability for authorized platforms to make payments on behalf of other Kashier merchants that are enabled for connected accounts. A platform is an aggregator of multiple merchants or service providers. Examples: Amazon, Talabat. A connected account refers to the merchants on the platform, such as McDonald's, KFC. ## Account requirements [#account-requirements] * Connected account: The connected account should be an existing Kashier merchant that is enabled for live payments. It must authorize the connection to the platform. * Platform account: The platform should be an existing Kashier merchant. No need for a bank account or live payment acceptance, but the connected accounts must authorize the platform to collect payments on their behalf (i.e., by displaying the connected account's payment UI on the platform app). ## Making payments on behalf of connected accounts [#making-payments-on-behalf-of-connected-accounts] Learn how to add the right information to your [Payment Sessions](/docs/accept-payments/payment-sessions) so you can make payments on behalf of your connected accounts. You can make a payment on behalf of a connected account by passing the connected account's merchant ID as `connectedAccount.merchantId` on the [Payment Sessions](/docs/accept-payments/payment-sessions) request. The value is the connected account's own MID and must carry the `MID-` prefix: ```json { "connectedAccount": { "merchantId": "MID-XXXXX-XXX" } } ``` Both the platform and sub-merchant can view transactions and export them from the [Merchant Portal](https://merchant.kashier.io). # Currency conversion (/docs/accept-payments/currency-conversion) Kashier exposes a lightweight exchange-rate lookup you can use to price cross-currency amounts before creating a payment — for example, quoting a customer a price in USD while the actual charge settles in EGP. This endpoint only reads a rate. It does not convert a payment, does not change how a payment session behaves, and is not switched on or off per merchant — calling it has no effect on anything else in your account. If you are looking for customer-facing display currencies on checkout, that is a separate, separately gated product feature; ask your account manager whether it is available to you. Unlike the rest of the payments API, this endpoint is **public and unauthenticated** — no `Authorization` or `api-key` header is required. You can call it directly from a browser or server without a merchant secret key. ## Get exchange rate [#get-exchange-rate] | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v3/payment/exchange-rate](https://test-api.kashier.io/v3/payment/exchange-rate) | | LIVE-URL | [https://api.kashier.io/v3/payment/exchange-rate](https://api.kashier.io/v3/payment/exchange-rate) | | Method | GET | ### Query parameters [#query-parameters] | Key | Description | | ---- | ------------------------------------------------------------------------------------------------ | | from | Source currency code (e.g. `USD`). Optional — defaults to `USD`. Returned as `base`. | | to | Target currency code (e.g. `EGP`). Optional — defaults to `EGP`. Becomes the key inside `rates`. | ```bash curl --location 'https://test-api.kashier.io/v3/payment/exchange-rate?from=USD&to=EGP' ``` ### Response [#response] ```json { "success": true, "timestamp": 1787654094144, "base": "USD", "date": "2026-08-25", "rates": { "EGP": 50.76884413 } } ``` | Field | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `success` | `true` when a rate was resolved. | | `timestamp` | When the rate was produced, in milliseconds since the epoch. | | `base` | The source currency — echoes `from`. | | `date` | The rate's date, `YYYY-MM-DD`. | | `rates` | An object with a single key: the `to` currency, mapped to the rate. Multiply an amount in `base` by this number to get the amount in `to`. | So `1 USD = 50.76884413 EGP` in the example above. Successful responses here are passed through from the upstream rate provider, so they have **no** `status`/`response`/`messages` wrapper — unlike the rest of the API. Errors *are* wrapped in the standard envelope. Parse the two shapes separately. ### Supported currencies [#supported-currencies] `from` and `to` accept ISO 4217 codes, and the range is wide — the rate comes from an upstream FX provider, not from a short Kashier list. `EGP`, `USD`, `EUR`, `GBP`, `SAR`, `AED`, `KWD`, `JPY`, `CAD`, `CHF`, and `AUD` all resolve, and so do many others. An unrecognised code is rejected rather than silently defaulted (see [Errors](#errors)). This endpoint quoting `USD → JPY` does not mean Kashier can take a payment in JPY. Payment sessions accept **`EGP`, `USD`, `GBP`, and `EUR`** only — see [create payment session](/docs/api-reference/payment-sessions/createPaymentSession). Use this endpoint to price or display a cross-currency amount; charge in a currency your account actually supports. ### Errors [#errors] An unknown `to` currency returns **404**: ```json { "error": { "cause": "Invalid Data" }, "messages": { "en": "The Exchange Rate Service is temporarily unavailable. Please try again.", "ar": "حدث خطأ ما" }, "status": "FAILURE" } ``` An unknown `from` currency, or an upstream outage, returns **503**: ```json { "error": { "cause": "Service unavailable" }, "messages": { "en": "Exchange rate is temporarily unavailable. Please try again later", "ar": "سعر الصرف غير متاح مؤقتًا. يرجى المحاولة مرة أخرى لاحقًا" }, "status": "FAILURE" } ``` Note that the 404 message says "temporarily unavailable" even though the cause is an invalid currency code — branch on the status code and `error.cause`, not on the message text. # Customers (/docs/accept-payments/customers) The Customers module in Kashier's API allows merchants to store, manage, and retrieve customer information. It enables the creation of customer profiles and links them to transactions at checkout. Merchants can update customer details, track payment history, and personalize the experience. This module simplifies recurring payments and improves customer insights for both businesses and customers. ## Customer object structure [#customer-object-structure] The customer interface. ```json { "_id": "67ba0311bf4f31001203c6c2", "name": "John Doe", "phoneNumber": "01XXXXXXXXX", "emailAddress": "", "customerId": "C-XXXXXXXXXXXXX", "merchantId": "MID-XXXXX-XXX", "customFields": [ { "name": "custom-key-1", "value": "custom-value-1" }, { "name": "custom-key-2", "value": "custom-value-2" } ], "createdByUserId": "5f8d0d55b54764421b7156c3", "__v": 0, "createdAt": "2025-02-22T17:02:10.098Z", "updatedAt": "2025-02-25T12:31:40.474Z", "id": "67ba0311bf4f31001203c6c2" } ``` ## List all customers [#list-all-customers] Retrieve a list of all customers. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST URL | [https://test-api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name](https://test-api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name) | | LIVE URL | [https://api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name](https://api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name) | | Method | GET | ### Query parameters [#query-parameters] All of these are optional — call the route with no query string to get the first page with the defaults. | Key | Description | | ----------------------- | --------------------------------------------------------- | | `page` | Page number. Default `1`. | | `limit` | Records per page. Default `20`. | | `sortBy` | Sort field: `name` or `createdAt`. | | `sortType` | Sort order: `1` ascending, `-1` descending. Default `-1`. | | `search` | Search term. | | `searchBy` | Field to search in, for example `name`. | | `startDate` / `endDate` | Date range filter, `YYYY-MM-DD`. | | `labels` | Comma-separated label names. | | `branchIds` | Comma-separated branch IDs. | ```bash curl --location 'https://test-api.kashier.io/v2/customers?page=1&limit=20&sortType=-1&sortBy=name' --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Full parameter and response reference → [List all customers](/docs/api-reference/customers/listCustomers). ## Get customer details [#get-customer-details] Retrieve details of a specific customer. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------- | | TEST URL | [https://test-api.kashier.io/v2/customers/:id](https://test-api.kashier.io/v2/customers/:id) | | LIVE URL | [https://api.kashier.io/v2/customers/:id](https://api.kashier.io/v2/customers/:id) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/customers/:id' --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-1] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Full parameter and response reference → [Get customer details](/docs/api-reference/customers/getCustomer). ## Add new customer [#add-new-customer] Add a new customer. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------ | | TEST URL | [https://test-api.kashier.io/v2/customers](https://test-api.kashier.io/v2/customers) | | LIVE URL | [https://api.kashier.io/v2/customers](https://api.kashier.io/v2/customers) | | Method | POST | ```bash curl --location 'https://test-api.kashier.io/v2/customers' --header 'Authorization: YOUR_TEST_SECRET_KEY' --data-raw '{ "name": "John doe", "phoneNumber": "01123456789", "emailAddress": "john.doe@example.com", "customFields": [ { "name": "custom-key-1", "value": "custom-value-1" } ], "preferredCommunicationChannel":"sms" }' ``` ### Headers [#headers-2] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Full parameter and response reference → [Add a new customer](/docs/api-reference/customers/createCustomer). ## Update customer [#update-customer] Update a customer. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------- | | TEST URL | [https://test-api.kashier.io/v2/customers/:id](https://test-api.kashier.io/v2/customers/:id) | | LIVE URL | [https://api.kashier.io/v2/customers/:id](https://api.kashier.io/v2/customers/:id) | | Method | PUT | ```bash curl --location --request PUT 'https://test-api.kashier.io/v2/customers/:id' --header 'Authorization: YOUR_TEST_SECRET_KEY' --data-raw '{ "name": "John doe", "phoneNumber": "01123456789", "emailAddress": "john.doe@example.com", "customFields": [ { "name": "custom-key-1", "value": "custom-value-1" } ], "preferredCommunicationChannel":"sms" }' ``` ### Headers [#headers-3] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Full parameter and response reference → [Update a customer](/docs/api-reference/customers/updateCustomer). ## Delete customer [#delete-customer] Delete a customer. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------- | | TEST URL | [https://test-api.kashier.io/v2/customers/:id](https://test-api.kashier.io/v2/customers/:id) | | LIVE URL | [https://api.kashier.io/v2/customers/:id](https://api.kashier.io/v2/customers/:id) | | Method | DELETE | ```bash curl --location --request DELETE 'https://test-api.kashier.io/v2/customers/:id' --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-4] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Full parameter and response reference → [Delete a customer](/docs/api-reference/customers/deleteCustomer). ## Bulk import [#bulk-import] The process for importing customers from an Excel sheet requires two sequential API calls. ### Upload Excel file API [#upload-excel-file-api] * Purpose: Upload and validate the Excel file containing customer data * Response: * If errors are found in the data, they will be returned for correction. * If validation passes, the API returns a `correlationId` * The `correlationId` serves as a reference to your validated data | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------- | | TEST URL | [https://test-api.kashier.io/v2/customers/import](https://test-api.kashier.io/v2/customers/import) | | LIVE URL | [https://api.kashier.io/v2/customers/import](https://api.kashier.io/v2/customers/import) | | Method | POST | ```bash curl --location --request POST 'https://test-api.kashier.io/v2/customers/import' --header 'Authorization: YOUR_TEST_SECRET_KEY' --form 'file=@"/home/boody/Downloads/customers.xlsx"' ``` #### Headers [#headers-5] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Full parameter and response reference → [Upload a customers sheet for review](/docs/api-reference/customers/importCustomers). ### Save customers API [#save-customers-api] * Purpose: Permanently save the validated customer data to the system * Required: Include the `correlationId` as a query string parameter * This API finalizes the import process using the previously validated data | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST URL | [https://test-api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId](https://test-api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId) | | LIVE URL | [https://api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId](https://api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId) | | Method | GET | ```bash curl --location 'https://test-api.kashier.io/v2/customers/savecustomers?correlationId={{correlationId}}' --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` #### Headers [#headers-6] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | Full parameter and response reference → [Save the uploaded customers](/docs/api-reference/customers/saveImportedCustomers). This two-step approach ensures data integrity by separating validation from the actual import process. # Flutter SDK (/docs/accept-payments/flutter-sdk) A complete, code-accurate guide to installing, configuring, and shipping Kashier payments in a Flutter app — Apple Pay, mobile wallets, and cards — from a single API call. | Field | Value | | -------- | -------------------------------------------------------------------------------------- | | Version | 0.1.1 | | Audience | Merchant developers integrating Kashier payments into Flutter apps | | Package | [pub.dev/packages/kashier\_flutter\_sdk](https://pub.dev/packages/kashier_flutter_sdk) | ## SDK overview and prerequisites [#sdk-overview-and-prerequisites] The Kashier Flutter SDK is a drop-in payment library that lets your Flutter app accept payments from Egyptian customers through Apple Pay, mobile wallets (Vodafone Cash and others), and credit / debit cards (with 3-D Secure and saved cards) — all from a single API call. The SDK ships its own pre-built payment sheet UI. You hand it a session ID from your backend, and it picks the right method, renders the right screens, handles 3DS WebViews, polls for wallet reconciliation, and calls back with a typed result. ### What you can do [#what-you-can-do] * Accept Apple Pay payments on iOS using your own Apple Pay Merchant ID (per-merchant — see the Apple Pay section). * Accept mobile wallet payments (Vodafone Cash and other R2P-compatible Egyptian wallets) with built-in polling. * Accept card payments (new card, saved card, 3-D Secure) without ever touching raw card data — the SDK handles tokenization and the 3DS WebView. * Switch between test and live environments at initialization time. * Render the SDK UI in Arabic or English, switchable at runtime. ### Supported platforms [#supported-platforms] | Requirement | Minimum | | --------------------- | ---------------------------------------------- | | Flutter | 3.22.0 | | Dart | 3.4.0 | | iOS deployment target | 14.0 | | Android | inherits Flutter default minSdk (typically 21) | | Apple device | iPhone with Face ID or Touch ID for Apple Pay | | Account | Active Kashier merchant account | ### Method and platform matrix [#method-and-platform-matrix] | Payment method | iOS | Android | | -------------------------------- | --- | ------------------------ | | Apple Pay | Yes | No (degrades gracefully) | | Mobile wallet (Vodafone Cash, …) | Yes | Yes | | Card (new) | Yes | Yes | | Card (saved) | Yes | Yes | | 3-D Secure | Yes | Yes | The SDK's native layer is iOS-only (Swift + PassKit for Apple Pay). Wallet and card flows are pure Dart (HTTP + WebView) and run on both platforms. ### Architecture at a glance [#architecture-at-a-glance] ```text ┌────────────────────────────────────────────────────────┐ │ Your Flutter App │ │ └── KashierPaymentProvider ◀─ wraps MaterialApp │ │ └── MaterialApp / Router │ └────────────────────────────────────────────────────────┘ │ ▼ KashierSDK.startPayment(sessionId, …) ┌────────────────────────────────────────────────────────┐ │ Kashier SDK (Dart) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Apple Pay │ │ Wallet │ │ Card + 3DS │ │ │ │ Handler │ │ Handler │ │ Handler │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ ▼ MethodChannel │ │ ┌──────────────────────────────────────────────────┐ │ │ │ KashierBridge (iOS only) → PassKit (Apple Pay) │ │ │ └──────────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────────┘ │ ▼ HTTPS ┌────────────────────────────────────────────────────────┐ │ Kashier API + FEP (test or live) │ └────────────────────────────────────────────────────────┘ ``` * Apple Pay crosses the native bridge three times (DOWN: present sheet with merchant ID, UP: encrypted token, DOWN: finalize sheet). * Wallet and card flows are pure Dart with HTTP + WebView — no native bridge needed. ### Prerequisites [#prerequisites] 1. An active Kashier merchant account. 2. Your Kashier API credentials (available in your Kashier Dashboard). 3. A backend server capable of creating payment sessions on Kashier's API. 4. For Apple Pay only: your own Apple Pay Merchant ID and a payment processing certificate. ### What is a session ID? [#what-is-a-session-id] A session ID is a unique identifier created by your backend that represents a single payment transaction. Your backend calls Kashier's [Payment Sessions API](/docs/accept-payments/payment-sessions) with the amount, currency, allowed methods, and customer details, and Kashier returns a session ID. Your Flutter app then passes only that session ID to the SDK — no API key, no Kashier merchant ID, no card data ever lives in the mobile binary. ```text Your Backend ──POST /sessions──▶ Kashier API │ session_id ◀────────────┘ │ ▼ Your Flutter app │ ▼ KashierSDK.startPayment(sessionId) Kashier SDK ``` Backend integration is out of scope for this guide. See the [Payment Sessions API documentation](/docs/accept-payments/payment-sessions) for backend details. ## Installation and setup [#installation-and-setup] ### Add the dependency [#add-the-dependency] Add the SDK from [pub.dev](https://pub.dev/packages/kashier_flutter_sdk) to your `pubspec.yaml`: ```yaml dependencies: kashier_flutter_sdk: ^0.1.1 ``` Then install: ```bash flutter pub get ``` The SDK pulls in these transitive dependencies (no action needed — listed for transparency): | Package | Version | Used for | | --------------------------- | ------- | ------------------------- | | http | ^1.2.0 | Kashier API / FEP calls | | webview\_flutter | ^4.0.0 | 3DS challenge container | | flutter\_inappwebview | ^6.0.0 | 3DS WebView | | connectivity\_plus | ^7.1.1 | Pre-flight offline checks | | flutter\_svg | ^2.0.10 | Sheet iconography | | plugin\_platform\_interface | ^2.0.2 | Platform channel contract | | meta | ^1.16.0 | `@internal` annotations | ### iOS setup [#ios-setup] Open `ios/Podfile` and ensure the platform is at least iOS 14.0: ```ruby platform :ios, '14.0' ``` Install the iOS pods: ```bash cd ios && pod install ``` Apple Pay requires per-merchant configuration (your own Apple Merchant ID, an Xcode entitlement, and a payment processing certificate). Follow the runbook in the Apple Pay section before enabling it. ### Android setup [#android-setup] The SDK requires only the standard INTERNET permission, which is already part of the default Flutter `AndroidManifest.xml`. No extra setup is needed. Apple Pay is iOS-only. The SDK has no native Android code — wallet and card flows are pure Dart. On Android, `KashierSDK.isApplePayAvailable()` returns false and the unified payment sheet automatically hides the Apple Pay option. ### Wrap your app with KashierPaymentProvider [#wrap-your-app-with-kashierpaymentprovider] The SDK renders its payment sheet on its own overlay, outside your app's Navigator. To enable that overlay, wrap your `MaterialApp` (or `CupertinoApp` / `MaterialApp.router`) with `KashierPaymentProvider`: ```dart import 'package:flutter/material.dart'; import 'package:kashier_flutter_sdk/kashier_flutter_sdk.dart'; void main() { runApp( KashierPaymentProvider( child: MaterialApp( home: HomeScreen(), ), ), ); } ``` This works with any navigation solution: Navigator 2.0, GoRouter, AutoRoute, nested navigators, etc. Why this is required: because the provider holds the overlay, you can call `KashierSDK.startPayment(...)` from anywhere — no BuildContext required. The SDK manages its own UI lifecycle and never pushes onto your routes. ### Initialize the SDK [#initialize-the-sdk] Call `KashierSDK.initialize()` once before starting any payment — typically at app startup or in the `initState` of the screen that triggers payment: ```dart KashierSDK.initialize( mode: KashierMode.test, // or KashierMode.live language: KashierLanguage.en, // or KashierLanguage.ar appleMerchantId: 'merchant.com.yourcompany.app', // iOS Apple Pay only ); ``` | Parameter | Type | Required | Default | Description | | --------------- | --------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | mode | KashierMode | yes | — | test (sandbox) or live (production) | | language | KashierLanguage | no | en | UI language for the payment sheet | | appleMerchantId | String? | no | null | Your own Apple Pay Merchant ID. Required only to enable Apple Pay; must match the Merchant ID in your Xcode entitlement. When omitted, Apple Pay is not offered. | Switching language at runtime: call `initialize()` again with the new `KashierLanguage`. There is no separate setter — re-initialization is the supported pattern. It disposes the previous API client and re-registers payment handlers. ## Quick start [#quick-start] End-to-end integration in under 60 lines: ```dart import 'package:flutter/material.dart'; import 'package:kashier_flutter_sdk/kashier_flutter_sdk.dart'; void main() { runApp( KashierPaymentProvider( child: const MyApp(), ), ); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: CheckoutScreen(), ); } } class CheckoutScreen extends StatefulWidget { @override State createState() => _CheckoutScreenState(); } class _CheckoutScreenState extends State { @override void initState() { super.initState(); KashierSDK.initialize( mode: KashierMode.test, language: KashierLanguage.en, appleMerchantId: 'merchant.com.yourcompany.app', // omit if not using Apple Pay ); } void _pay() { KashierSDK.startPayment( sessionId: 'sess_abc123', // from your backend onSuccess: (KashierPaymentResult result) { print('Paid: ${result.transactionId}'); }, onPending: (KashierPaymentPending pending) { print('Unconfirmed — reconcile server-side: ${pending.orderId}'); }, onFailure: (KashierPaymentError error) { if (error.code == KashierErrorCode.userCancelled) return; print('Failed: ${error.message}'); }, ); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: ElevatedButton( onPressed: _pay, child: const Text('Pay'), ), ), ); } } ``` That's the entire integration. The SDK reads the session, renders the method-selection sheet (Apple Pay, Wallet, Card — whichever the session and device support), runs the chosen flow, and fires exactly one callback when done. Unified payment method selection sheet A complete runnable sample lives in `example/lib/main.dart`. ## Apple Pay integration guide [#apple-pay-integration-guide] Apple Pay is iOS-only. Unlike wallet and card, it requires one-time per-merchant setup: you bring your own Apple Pay Merchant ID and pass it to `initialize()`. The unified payment sheet is the recommended path because it handles availability, fallback, and method selection automatically. Use the standalone Apple Pay button only if you want to launch Apple Pay directly without showing a method picker. ### The per-merchant model [#the-per-merchant-model] Each merchant uses their own Apple Pay Merchant ID — there is no shared/bundled Kashier identifier. The ID you register with Apple is: 1. Passed to the SDK via `KashierSDK.initialize(appleMerchantId: …)`. 2. Carried DOWN to native PassKit and set as `PKPaymentRequest.merchantIdentifier`. 3. Backed by your own Apple Pay Payment Processing Certificate so Kashier can decrypt the resulting token for your account. If `appleMerchantId` is null or empty, the SDK cannot build a valid Apple Pay request — `isApplePayAvailable()` returns false and Apple Pay is hidden from the sheet. ### Enable and configure runbook [#enable-and-configure-runbook] Complete these steps once per merchant app. Steps that involve Kashier-side certificate handling are flagged Preview. 1. Register an Apple Pay Merchant ID. Apple Developer portal → Certificates, Identifiers & Profiles → Identifiers → Merchant IDs → +. Use a reverse-DNS identifier, e.g. `merchant.com.yourcompany.app`. 2. Create a Payment Processing Certificate. \[Preview — coordinate with Kashier, early access] Apple requires a Certificate Signing Request (CSR) tied to the processor that will decrypt the token. Open a ticket with Kashier support to obtain the CSR / certificate for your Merchant ID before going live. This onboarding is currently manual. 3. Add the Apple Pay capability in Xcode. Open `ios/Runner.xcworkspace` → select the Runner target → Signing & Capabilities → + Capability → Apple Pay, then check your Merchant ID. This writes the entitlement into `ios/Runner/Runner.entitlements`: ```xml com.apple.developer.in-app-payments merchant.com.yourcompany.app ``` 4. Pass the Merchant ID to the SDK. The value here must match the entitlement exactly: ```dart KashierSDK.initialize( mode: KashierMode.live, appleMerchantId: 'merchant.com.yourcompany.app', ); ``` 5. Verify availability on a physical device with a card in Apple Wallet: ```dart final ok = await KashierSDK.isApplePayAvailable(); // expect true ``` If `isApplePayAvailable()` returns false after setup, check (in order): you passed `appleMerchantId`; it matches the entitlement; you're on a real device (not Simulator); a supported card is in Apple Wallet. ### Check availability [#check-availability] ```dart final available = await KashierSDK.isApplePayAvailable(); if (available) { // Render Apple Pay button or include in payment sheet } else { // Fall back to other methods } ``` `isApplePayAvailable()` returns false when: * `appleMerchantId` was not supplied to `initialize()` (or is empty). * Running on Android or any non-iOS platform. * Running on iOS Simulator (no real Wallet). * The user has not added any supported card to Apple Wallet. * The device hardware does not support Apple Pay. ### Display the Apple Pay button (optional, direct-launch) [#display-the-apple-pay-button-optional-direct-launch] The SDK ships a styled `KashierApplePayButton` widget for merchants who want to skip the method-selection sheet: ```dart KashierApplePayButton( onPressed: () { KashierSDK.startPayment( sessionId: 'sess_abc123', onSuccess: (result) { /* … */ }, onFailure: (error) { /* … */ }, ); }, height: 50, // optional, default 50 cornerRadius: 10, // optional, default 10 ) ``` Apple Pay button in the sheet The button label flips automatically between English (Pay with Pay) and Arabic (ادفع مع Pay). The Apple logo glyph (U+F8FF) renders correctly on Apple devices. The button is a Flutter widget (not the native PKPaymentButton) so it renders identically across iOS versions and respects your theme. Tapping it does not start a payment automatically — wire that in `onPressed`, typically by calling `KashierSDK.startPayment(...)`. ### End-to-end Apple Pay flow [#end-to-end-apple-pay-flow] The validation handshake has been removed — the flow now crosses the native bridge three times: | # | Direction | Bridge call | Payload | | - | ------------ | ---------------------------- | ----------------------------------------------------------------------------------- | | 1 | Dart → Swift | presentApplePay | amount, currency, storeName, supportedNetworks, merchantId | | 2 | Swift → Dart | token callback | `{ paymentData, paymentMethod{displayName, network, type}, transactionIdentifier }` | | 3 | Dart → Swift | completePaymentAuthorization | `{ success: bool }` (finalizes the sheet) | Native PassKit builds the PKPaymentRequest with `merchantIdentifier = merchantId`, `countryCode = "EG"`, `merchantCapabilities = .capability3DS`, and the resolved networks (visa, mastercard, meeza, amex). ### Callbacks [#callbacks] | Event | Callback fired | Notes | | -------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------- | | FEP returns 200 | `onSuccess(KashierPaymentResult)` | `result.card.wallet == "APPLE_PAY"` | | User taps Cancel in Apple Pay sheet | onFailure | code == userCancelled. Don't show error UI — the user knows they cancelled | | Token processing failure | onFailure | tokenProcessingFailed — FEP could not process the token | | Misconfiguration / Simulator / missing merchant ID | onFailure | applePayConfigError or applePayNotAvailable | Apple Pay never fires `onPending`. ### Platform fallback [#platform-fallback] On Android (or any non-iOS platform), `KashierApplePayButton` still renders but `onPressed` will surface applePayNotAvailable from `startPayment`. Use `isApplePayAvailable()` to gate the button, or rely on the unified payment sheet which hides Apple Pay automatically on unsupported platforms. ## Unified payment sheet [#unified-payment-sheet] The recommended integration path. One call, every method, automatic device gating. ```dart KashierSDK.startPayment( sessionId: 'sess_abc123', onSuccess: (result) { /* … */ }, onFailure: (error) { /* … */ }, onPending: (pending) { /* … */ }, // terminal, for card + wallet flows ); ``` The SDK: 1. Fetches session details from Kashier (`GET /sessions/{id}`). 2. Computes which methods to show by intersecting registered methods, session-allowed methods, and device capability. 3. Renders the method-selection sheet on its own overlay. 4. Runs the chosen flow (Apple Pay / Wallet / Card) end-to-end. 5. Calls one of `onSuccess`, `onFailure`, or `onPending`. For both card and wallet, `onPending` is terminal. ### How method visibility is decided [#how-method-visibility-is-decided] A method appears in the sheet only if all three are true: | Check | Source | Notes | | ---------------------- | ---------------------------------- | ----------------------------------------------------------- | | Registered in SDK | `KashierSDK._registerHandler(...)` | applePay, wallet, card all registered in v1 | | Allowed by the session | `session.allowedMethods` | Empty list = all methods allowed | | Available on device | `isAvailable()` | e.g., Apple Pay → merchant ID set + PassKit + cards present | Special case: Apple Pay is unlocked when `allowedMethods` contains either "applePay" or "card" — Apple Pay is treated as a card method on the backend. ### Session knobs that affect the sheet [#session-knobs-that-affect-the-sheet] Set these on your backend when creating the session (see [Payment Sessions API](/docs/accept-payments/payment-sessions)): | Field | Type | Effect | | ----------------- | ---------------------- | ----------------------------------------------------------------------------------------------------- | | allowedMethods | comma-separated string | Which methods appear: "APPLEPAY,WALLET,CARD". Omit or send empty to allow all. | | supportedNetworks | list | Card networks for Apple Pay (visa, mastercard, meeza). Defaults to all three. | | enable3DS | bool | Whether card payments require 3-D Secure | | saveCard | string | "optional" (checkbox shown), "forced" (always saves), "hidden" (no save UI), or omit (same as hidden) | | retrieveSavedCard | bool | Whether to fetch the customer's saved cards into the picker | | brandColor | hex string | Accent color for the payment sheet (see below) | | customer | object | Customer info displayed and posted with the card payment | `brandColor` formats accepted: 6-digit RRGGBB or 8-digit AARRGGBB, with or without a leading # (e.g., "#FF5733", "FF5733", "#CCFF5733"). A 6-digit value is treated as fully opaque. Null, empty, or unparseable values fall back to the Kashier primary color #001F5F. ### Localization [#localization] Sheet headers, button labels, success / failure copy, error messages, and pending copy all flip to Arabic when the SDK is initialized with `KashierLanguage.ar`. Supported languages: en, ar. ### Sheet lifecycle [#sheet-lifecycle] * The sheet appears above your existing UI on the `KashierPaymentProvider` overlay. * Only one sheet can be visible at a time. * The sheet dismisses itself when the flow completes (success, failure, or user dismissal). * For wallet payments, the sheet remains visible while polling. ## Wallet payment integration guide [#wallet-payment-integration-guide] Mobile-wallet payments (Vodafone Cash and other Egyptian R2P-compatible wallets) are an out-of-band flow: your customer enters their phone number in the SDK, then completes the payment in their wallet's own app. The SDK polls Kashier for reconciliation and fires `onSuccess` or `onFailure` when the wallet confirms. If reconcile polling runs the full 5 minutes without a terminal result, the SDK shows an "I have paid" button; tapping it fires the terminal `onPending` callback. Wallet phone-number entry ### Wallet flow [#wallet-flow] ```text 1. User picks "Wallet" in the unified sheet. 2. Sheet shows phone-number input. 3. SDK calls INITIATE_R2P (POST /v3/orders, apiOperation=INITIATE_R2P) — no callback fires yet. 4. User opens their wallet app and approves the charge. 5. SDK polls reconcile on a fixed schedule capped at 5 minutes total: - Phase 1: 12 attempts, 10s apart (120s). - Phase 2: increasing intervals (30s, 45s, 60s, …) until the 5-min cap. 6. Outcome: a. Confirmed → onSuccess(KashierPaymentResult) b. Declined → onFailure(KashierPaymentError) c. Still pending at the 5-min cap → SDK shows an "I have paid" button. Customer taps it → terminal onPending(KashierPaymentPending) fires (if implemented) and the sheet closes. Cancel → onFailure(userCancelled). ``` Wallet waiting sheet with I have paid Polling schedule, precisely: Phase 1 is 12 fixed 10-second intervals (120s). Phase 2 intervals are 30s, 45s, 60s, then 90s/120s/180s — but the SDK never dispatches a request that would cross the 300-second (5-minute) hard cap, so only the 30s/45s/60s Phase-2 attempts are reachable in practice. The cap is 5 minutes total, not 10. ### Wiring onPending [#wiring-onpending] `onPending` is optional and terminal for wallet: it fires at most once, only when reconcile polling exhausts after 5 minutes with the payment still pending and the customer taps "I have paid". It does not fire at initiation, and no `onSuccess`/`onFailure` follows it. ```dart KashierSDK.startPayment( sessionId: 'sess_abc123', onSuccess: (KashierPaymentResult result) { // Wallet confirmed the payment showSuccessScreen(result); }, onPending: (KashierPaymentPending pending) { // Polling exhausted while still pending and the customer asserted payment. // Confirm final settlement server-side using the orderId. print('Pending order: ${pending.orderId}'); // authoritative reference print('Status: ${pending.message}'); // localized }, onFailure: (KashierPaymentError error) { showErrorScreen(error); }, ); ``` Card vs wallet pending: the same `onPending` callback has unified semantics across card and wallet — it is terminal in both, firing after reconcile polling exhausts while the payment is still settling. For both, `orderId` is authoritative, `transactionId` may be empty, and you should confirm final settlement via webhook/backend. ### What KashierPaymentPending contains [#what-kashierpaymentpending-contains] | Field | Type | Description | | ------------- | ------- | ----------------------------------------------------- | | orderId | String | Kashier order ID (authoritative reference) | | transactionId | String | Transaction ID (e.g., "TX-35524544764"; may be empty) | | message | String | Status message (localized to SDK language) | | messageEn | String? | English status message | | messageAr | String? | Arabic status message | ### Wallet provider hint (KashierWalletProvider) [#wallet-provider-hint-kashierwalletprovider] ```dart enum KashierWalletProvider { vodafoneCash, // displayNameEn: "Vodafone Cash" | displayNameAr: "فودافون كاش" other, // displayNameEn: "Mobile Wallet" | displayNameAr: "محفظة إلكترونية" } ``` The SDK uses this enum internally to label wallet methods in the sheet. You do not need to instantiate it. ### Polling cap and webhook fallback [#polling-cap-and-webhook-fallback] The SDK polls for up to 5 minutes total. If the wallet user does not complete in time and the payment is still pending at the cap, the SDK shows the "I have paid" button (→ terminal `onPending`); an outright timeout surfaces `onFailure(paymentTimeout)`. The session itself may still settle later — your backend should rely on the server-to-server webhook (`session.serverWebhook`) as the source of truth, not solely on the SDK callback. ## Card payment integration guide [#card-payment-integration-guide] Card payments handle four sub-flows from a single entry point: new card, saved card, 3-D Secure, and (optionally) save-card-after-payment. The unified sheet renders the card form, runs validation, opens the 3DS WebView when the issuer requires it, reconciles, and reports back via `onSuccess` / `onFailure` / `onPending`. New-card form with save-card checkbox ### New-card flow [#new-card-flow] 1. User picks "Card" in the sheet → card form appears. 2. SDK validates card number, expiry, CVV, holder name inline before submitting. 3. SDK posts to Kashier card endpoint, receives the card response. 4. If 3DS is required → SDK opens an in-sheet WebView at the issuer's redirect URL. 5. After the OTP is submitted, the SDK reconciles on the post-OTP schedule. 6. SDK fires `onSuccess`, `onFailure`, or terminal `onPending` (if reconcile exhausts while still settling). ### Saved-card flow [#saved-card-flow] When `session.retrieveSavedCard == true`, the sheet shows the customer's previously-saved cards as selectable chips: 1. User taps a saved card → CVV-only prompt appears (CVV is never stored). 2. SDK posts the saved card token + CVV → same 3DS / reconcile flow as new card. 3. SDK fires `onSuccess` / `onFailure` / `onPending`. Saved cards accordion with inline CVV ### Save-card-after-payment [#save-card-after-payment] Controlled by `session.saveCard`: | Value | Behavior | | ---------- | --------------------------------------------------- | | "optional" | Save-card checkbox shown; user opts in | | "forced" | Card is always saved (checkbox shown but locked on) | | "hidden" | No save UI | | null | Same as "hidden" — no save behavior, no UI | The customer's saved cards live on Kashier's side, keyed by the customer reference in the session. They are never persisted in the SDK or your app. ### 3-D Secure (3DS) and reconciliation [#3-d-secure-3ds-and-reconciliation] When the issuer mandates 3DS, the SDK opens the issuer's challenge URL in an embedded flutter\_inappwebview view. 3DS challenge WebView After the user submits the OTP, the WebView closes when Kashier's redirect URL is hit and the SDK reconciles on the post-OTP schedule: * Post-OTP reconcile: 8 attempts over \~60s — four 5-second intervals, then four 10-second intervals. * If a terminal result arrives → `onSuccess` / `onFailure`. * If the schedule exhausts while still settling → terminal `onPending(KashierPaymentPending)`. If the user cancels the 3DS challenge (taps the close button), the SDK does not fail immediately. It switches to a "verifying" view and runs a cancel safety-net reconcile — 5 attempts at 0s, 5s, 10s, 15s, 30s (\~60s) — in case the payment actually went through. Terminal result found → `onSuccess` / `onFailure`; nothing found after exhaustion → `onFailure(userCancelled)`. If 3DS is disabled on the session (`session.enable3DS == false`) or not required by the issuer, the SDK skips the WebView entirely. ### What can fail [#what-can-fail] | Error code | Cause | | -------------------- | -------------------------------------------------------------- | | paymentDeclined | Issuer or processor declined the card | | authorizationFailed | 403 from Kashier (invalid hash, bad merchant config) | | authenticationFailed | 3DS challenge failed (e.g., wrong OTP) | | invalidCardDetails | Card field validation failed before submit | | cardRetrievalFailed | Could not load saved cards (sheet falls back to new-card form) | | cardDeletionFailed | Could not delete a saved card from the picker | | networkError | No internet at the time of the call | All map to `KashierPaymentError` with a localized message and optional issuer responseCode. ### PCI scope [#pci-scope] Card data is collected by the SDK's own form, tokenized against Kashier FEP, and never persisted in your app. Your app stays out of PCI scope as long as you use the SDK's card form — do not build your own card form against the same endpoints. ## Complete API reference [#complete-api-reference] Everything below is exported from a single import: ```dart import 'package:kashier_flutter_sdk/kashier_flutter_sdk.dart'; ``` Public surface: `KashierSDK`, `KashierPaymentProvider`, `KashierApplePayButton`, `KashierMode`, `KashierLanguage`, `KashierWalletProvider`, `KashierErrorCode`, `KashierPaymentResult`, `KashierCardInfo`, `KashierPaymentError`, `KashierPaymentPending`. ### KashierSDK (static API) [#kashiersdk-static-api] The main entry point. All members are static. #### initialize [#initialize] ```dart static void initialize({ required KashierMode mode, KashierLanguage language = KashierLanguage.en, String? appleMerchantId, }) ``` Initializes the SDK. Must be called once before any other method. Re-call to switch language or mode at runtime; this disposes the previous internal API client and re-registers payment handlers. Returns void. #### startPayment [#startpayment] ```dart static Future startPayment({ required String sessionId, required void Function(KashierPaymentResult result) onSuccess, required void Function(KashierPaymentError error) onFailure, void Function(KashierPaymentPending pending)? onPending, }) ``` Starts a payment flow for the given session. Fetches the session, picks available methods, shows the unified sheet, and reports the outcome via callbacks. Returns `Future` (resolves when the SDK has handed off to the sheet — not when payment completes). If `initialize()` was not called, fires `onFailure(unknownError)`. #### isApplePayAvailable [#isapplepayavailable] ```dart static Future isApplePayAvailable() ``` Checks whether Apple Pay is available on the current device (merchant ID set + iOS + PassKit + at least one card). Returns false on non-iOS platforms, if `appleMerchantId` was not supplied, or if the SDK is not yet initialized. #### Static getters [#static-getters] | Getter | Type | Description | | ------------- | --------------- | ------------------------------------------- | | isInitialized | bool | true after `initialize()` has been called | | mode | KashierMode? | Current mode, or null before initialization | | language | KashierLanguage | Current display language (defaults to en) | ### Enums [#enums] `KashierMode` — test and live environments: | Value | baseUrl | fepBaseUrl | | ----- | ---------------------------------------------------------- | ---------------------------------------------------------- | | test | [https://test-api.kashier.io](https://test-api.kashier.io) | [https://test-fep.kashier.io](https://test-fep.kashier.io) | | live | [https://api.kashier.io](https://api.kashier.io) | [https://fep.kashier.io](https://fep.kashier.io) | `KashierLanguage` — en (`Locale('en')`) and ar (`Locale('ar')`). `KashierWalletProvider` — vodafoneCash ("Vodafone Cash" / "فودافون كاش") and other ("Mobile Wallet" / "محفظة إلكترونية"). ### Models [#models] `KashierPaymentResult` — returned by `onSuccess` on a terminal successful payment: | Field | Type | Description | | ------------------- | ---------------- | -------------------------------------------- | | sessionId | String | The session this result belongs to | | status | String | Payment status ("SUCCESS", "CAPTURED", etc.) | | orderId | String? | Kashier order ID (systemOrderId) | | orderReference | String? | Merchant order reference | | transactionId | String? | Transaction ID (e.g., "TX-35524544753") | | authorizationNumber | String? | Issuer authorization number | | amount | num? | Charged amount | | currency | String? | ISO 4217 currency code (e.g., "EGP") | | card | KashierCardInfo? | Card / wallet details used | | message | String? | Localized success message (English) | | messageAr | String? | Localized success message (Arabic) | | transactionData | Map? | Raw API response body for advanced use | `KashierCardInfo` — accessed via `KashierPaymentResult.card`: brand (e.g. "Visa"), maskedNumber (e.g. "541674\*\*\*\*\*\*7777"), wallet (e.g. "APPLE\_PAY"). `KashierPaymentError` — returned by `onFailure`: code (KashierErrorCode), message (English), messageAr, responseCode (issuer code e.g. "57"), details. Construct via `KashierPaymentError.fromCode(KashierErrorCode.networkError)`. ## Error handling guide [#error-handling-guide] All failures (including user cancellation) flow through `onFailure(KashierPaymentError)`. Inspect `error.code` to decide how to respond. ### Full error code table [#full-error-code-table] | Code | Meaning | When it fires | Recoverable? | Recommended UX | | ------------------------ | ---------------------------------------------- | ------------------------------------------------- | ------------ | ------------------------------------------- | | sessionExpired | Session is no longer valid | Session timed out before completion | No | Mint a new session on the backend, restart | | sessionNotFound | Session ID not recognised | Wrong / mistyped ID, or wrong env | No | Verify the ID and that mode matches backend | | applePayNotAvailable | Apple Pay not available on device | Non-iOS, no merchant ID, no cards, Simulator | No | Hide Apple Pay; offer wallet / card | | userCancelled | User dismissed the payment sheet | User taps Cancel | Yes | Show no error UI — silently return | | validationFailed | Apple Pay validation failed | Pre-flight validation rejected | No | Contact Kashier support; backend issue | | applePayConfigError | Apple Pay misconfiguration | Wrong/absent merchant ID, missing cert, Simulator | No | Re-check the runbook; contact support | | merchantValidationFailed | Apple Pay validation failed during the session | Mid-flow validation issue | No | Contact Kashier support | | tokenProcessingFailed | FEP could not process the Apple Pay token | FEP rejected the encrypted token | No | Retry; if persistent, contact support | | paymentDeclined | Issuer / processor declined the card | Insufficient funds, fraud rules, etc. | Yes | Offer to retry with a different card | | authorizationFailed | HTTP 403 — authentication failed | Invalid hash, missing credentials | No | Backend config issue; not user-recoverable | | networkError | No internet connection | Pre-flight or mid-flight network drop | Yes | Surface "check connection, retry" | | paymentTimeout | Wallet polling cap reached | Wallet not completed within the 5-minute cap | Yes | Show retry; webhook may still confirm later | | walletInitFailed | INITIATE\_R2P API call failed | Wallet provider unavailable | Yes | Retry, or offer card | | cardRetrievalFailed | Could not fetch saved cards | Backend or network error | Yes | Sheet falls back to new-card form | | cardDeletionFailed | Could not delete a saved card | Backend or network error | No | Show "try again later" | | invalidCardDetails | Card form validation failed | Invalid PAN, expiry, CVV, etc. | Yes | Re-render the form with field errors | | authenticationFailed | 3-D Secure failed | Wrong OTP, bank decline | Yes | Retry the card or offer another | | unknownError | Unclassified (incl. SDK not initialized) | Any uncaught path | No | Log error.details; contact support | Error result sheet ### Special-case patterns [#special-case-patterns] * userCancelled: never show an error toast / dialog. The user knows they cancelled — `if (error.code == KashierErrorCode.userCancelled) return;` * networkError: the SDK pre-checks connectivity via connectivity\_plus. On a drop, surface a retry button. * paymentTimeout (wallet only): the polling cap is 5 minutes. The session may still settle — treat the server webhook as the source of truth. * sessionExpired / sessionNotFound: the user can't recover; your app must mint a new session and restart. ### Localization of error copy [#localization-of-error-copy] Every error carries message (English) and messageAr (Arabic): ```dart final isAr = KashierSDK.language == KashierLanguage.ar; final copy = isAr ? (error.messageAr ?? error.message) : error.message; ``` ### Diagnostics [#diagnostics] When opening a Kashier support ticket about a failure, include `error.code.name`, `error.message`, `error.responseCode`, `error.details`, the session ID and approximate timestamp. ## Callbacks and results [#callbacks-and-results] `startPayment` resolves to exactly one of three terminal callbacks. Wire all three (`onPending` optional) and confirm settlement server-side for pending. | Callback | Type | Fires when | | --------- | --------------------- | ---------------------------------------------------------------------------------------------- | | onSuccess | KashierPaymentResult | Payment confirmed (card / wallet / Apple Pay) | | onPending | KashierPaymentPending | Card 3DS reconcile exhausted, OR wallet 5-min poll exhausted + "I have paid" tapped. Terminal. | | onFailure | KashierPaymentError | Any failure, including userCancelled | Success result sheet Card 3DS pending screen Card pending and wallet pending render on distinct surfaces, but both deliver the same terminal `onPending(KashierPaymentPending)`. Always reconcile final settlement using `pending.orderId` via your webhook/backend. ## Testing guide [#testing-guide] ### Test mode [#test-mode] ```dart KashierSDK.initialize(mode: KashierMode.test); ``` Test mode endpoints: API [https://test-api.kashier.io](https://test-api.kashier.io), FEP [https://test-fep.kashier.io](https://test-fep.kashier.io). Make sure your backend mints sessions against the matching test API and returns test-mode session IDs. A live session in test mode will fail with sessionNotFound. ### Apple Pay testing [#apple-pay-testing] Apple Pay testing requires a physical iOS device (not the Simulator), the full setup complete (merchant ID + entitlement + certificate), and an Apple sandbox tester account with at least one test card in Apple Wallet. 1. Complete the runbook and pass `appleMerchantId` to `initialize()`. 2. Create a sandbox tester at [App Store Connect](https://appstoreconnect.apple.com/access/users) → Sandbox Testers. 3. Sign into the sandbox account on the device under Settings → App Store → Sandbox Account. 4. Add a test card to Apple Wallet — see Apple's [Sandbox Testing for Apple Pay](https://developer.apple.com/apple-pay/sandbox-testing/). 5. Run your Flutter app on the device, open the SDK sheet, and complete the flow. Simulator and a missing/mismatched merchant ID both return applePayNotAvailable. There is no way to test Apple Pay end-to-end on the Simulator. ### Wallet testing [#wallet-testing] Use Kashier-provided sandbox phone numbers for Vodafone Cash and other wallets. The wallet flow is asynchronous: after entering the test number, the SDK polls reconcile and fires `onSuccess` (or a configured failure) within a few seconds in test mode. If you let polling run the full 5 minutes without a terminal result, the SDK shows the "I have paid" button, and tapping it fires the terminal `onPending`. ### Card testing [#card-testing] Use standard Kashier test PANs to exercise success, decline, and 3DS-required paths. | Scenario | Expected outcome | | ------------------------------------- | ---------------------------------------------------- | | Success | onSuccess | | Generic decline | onFailure(paymentDeclined) | | 3DS required → success | 3DS WebView → onSuccess | | 3DS required → wrong OTP | 3DS WebView → onFailure(authenticationFailed) | | 3DS cancelled but charge went through | cancel reconcile → onSuccess | | 3DS cancelled, nothing settled | cancel reconcile exhausts → onFailure(userCancelled) | | Reconcile exhausts while settling | onPending | | Insufficient funds | onFailure(paymentDeclined) with issuer code | ### Going live [#going-live] 1. Switch to live mode: `KashierSDK.initialize(mode: KashierMode.live, appleMerchantId: '…')`. 2. Verify your backend uses live Kashier API credentials and creates sessions against [https://api.kashier.io](https://api.kashier.io). 3. Confirm your Apple Pay Payment Processing Certificate is provisioned with Kashier. 4. Run a real, low-value smoke transaction through each method you support. 5. Ensure your backend webhook handler treats Kashier as the source of truth — do not rely solely on the mobile SDK callback. 6. Test the failure paths once more in live mode (cancellation, declined card). ## Frequently asked questions [#frequently-asked-questions] **Do I need my own Apple Pay merchant ID or certificate?** Yes. Apple Pay is per-merchant: register your own Apple Pay Merchant ID, add it to your Xcode entitlement, and pass it to `KashierSDK.initialize(appleMerchantId: …)`. You also need a Payment Processing Certificate provisioned with Kashier so your tokens can be decrypted — this onboarding is currently manual (Preview); open a Kashier support ticket. **Why doesn't startPayment need a BuildContext?** Because `KashierPaymentProvider` holds a global overlay reference. The SDK pushes its sheet onto that overlay rather than your Navigator, so it works from any layer of your app — including background callbacks, Bloc handlers, services, etc. **Can I switch language at runtime?** Yes — call `KashierSDK.initialize(mode: ..., language: KashierLanguage.ar)` again with the new language. This re-creates the internal API client and re-registers payment handlers. **What happens if the user cancels Apple Pay?** `onFailure` fires with `KashierErrorCode.userCancelled`. Suppress your error UI for that code — treat it the same as a benign back-tap. **My wallet payment never fires onSuccess. Why?** The customer hasn't completed the payment in their wallet app. The SDK polls Kashier for up to 5 minutes; if still pending at the cap it shows "I have paid" (→ terminal `onPending`), and an outright timeout surfaces `onFailure(paymentTimeout)`. The session may still settle later — treat the server webhook as authoritative. **What happens if the user cancels the 3DS challenge?** The SDK doesn't fail immediately. It runs a short cancel safety-net reconcile (5 attempts over \~60s) in case the payment actually went through, then fires `onSuccess`/`onFailure` if a result is found, or `onFailure(userCancelled)` if nothing settled. **How do I customize the sheet's brand color?** Set `brandColor` on the session when your backend creates it. Accepts 6-digit RRGGBB or 8-digit AARRGGBB, with or without #. Invalid values fall back to Kashier's primary color. **The Apple Pay button doesn't show on Android. Is that a bug?** No — Apple Pay is iOS-only. The button still renders on Android but tapping it surfaces applePayNotAvailable. The unified payment sheet hides Apple Pay entirely on Android. **Where do I get a session ID?** Your backend creates one by calling Kashier's [Payment Sessions API](/docs/accept-payments/payment-sessions). Backend integration is out of scope for this guide. **Is the SDK PCI-compliant?** Card data never touches your app's process. The SDK collects card input in its own form and tokenizes directly against Kashier FEP. Your app stays out of PCI scope as long as you use the SDK's card form. **Can I show my own custom payment UI instead of the SDK's sheet?** Not supported in v1. The SDK owns the sheet UI to ensure consistent behavior across methods. If you need a deeply custom flow, contact Kashier support. **Does the SDK support recurring or stored-credential payments?** Yes — through the save-card feature. Set `session.saveCard = "optional"` or "forced" and `session.retrieveSavedCard = true` on subsequent sessions for the same customer. The SDK handles tokenization; your backend issues the recurring charge against the saved token. **Does the SDK work with MaterialApp.router (GoRouter, AutoRoute)?** Yes. `KashierPaymentProvider` is independent of routing — wrap whichever app widget you use. The SDK overlay sits above the router's navigation stack. **What happens if the device goes offline mid-payment?** The SDK pre-checks connectivity (via connectivity\_plus) before each API call and surfaces `KashierErrorCode.networkError` if offline. Mid-flight network drops are caught and reported the same way. **Can two startPayment calls run at once?** No — only one sheet can be visible at a time. Call `startPayment` once and wait for one of the callbacks to fire before calling again. # Accept payments (/docs/accept-payments) Kashier gives you several ways to accept a payment. Pick the one that matches how much you want to build: | You want | Use | Effort | | --------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------ | | A checkout page hosted by Kashier, reached by an API call | [Payment sessions](/docs/accept-payments/payment-sessions) | Low | | The checkout embedded in your own page (iframe) | [Embed the checkout](/docs/accept-payments/payment-sessions#step-2-send-the-customer-to-pay) | Low | | A payment page you can share by link, with no code at all | [Payment links](/docs/accept-payments/payment-links) | None | | Apple Pay directly on your site | [Apple Pay](/docs/accept-payments/apple-pay) | Medium | | A QR code or request to pay over InstaPay | [InstaPay](/docs/accept-payments/instapay) | Medium | | Egyptian Meeza cards, submitted as ordinary cards | [Meeza](/docs/accept-payments/meeza) | None | | Charging customers on a schedule | [Recurring payments](/docs/accept-payments/recurring) | Medium | | Full control over the card form and payment flow | [Direct API integration](/docs/direct-api) | High | Before building the payment, a couple of supporting tools are worth knowing about: * [Currency conversion](/docs/accept-payments/currency-conversion) — look up live exchange rates for cross-currency pricing. * [Product catalog](/docs/accept-payments/products) — build itemized payment links and checkout pages from products instead of a flat amount. After the payment, you can manage the full lifecycle: * [Authorize and capture](/docs/accept-payments/authorize-capture) — hold funds first, capture later. * [Transactions](/docs/accept-payments/transactions) — query payment status and history. * [Refunds](/docs/accept-payments/refunds) and [void](/docs/accept-payments/void). * [Settlement](/docs/accept-payments/settlement) and [order reconciliation](/docs/accept-payments/order-reconciliation). * [Webhooks](/docs/webhooks) — get notified server-to-server about every event. # InstaPay (/docs/accept-payments/instapay) Collect a payment over InstaPay, Egypt's instant payment network, by generating a QR code the customer scans or by pushing a request to pay to a payer address you already know. InstaPay is a first-class payment method: you select it with `paymentMethod.type = "instapay"`. It is a collection rail, not a card rail, so it has no tokenization, no recurring and no authorize/capture. It does support refund, cancel and transaction force closure. The flow is: 1. You initiate a collection — `GENERATE_QR` for a scan-to-pay QR code, or `INITIATE_R2P` to push a pay request to a payer address. 2. The customer approves the request in their bank or wallet app. 3. Kashier notifies you through the standard server [webhook](/docs/webhooks). ## Initiate a collection [#initiate-a-collection] An InstaPay collection is a [Direct API](/docs/direct-api) order: you post it to the same `POST /v3/orders` endpoint used for cards and wallets, and pick the collection method with `apiOperation`. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------ | | TEST-URL | [https://test-fep.kashier.io/v3/orders](https://test-fep.kashier.io/v3/orders) | | LIVE-URL | [https://fep.kashier.io/v3/orders](https://fep.kashier.io/v3/orders) | | Method | POST | ### Headers [#headers] | Key | Description | | ------------ | ------------------------------------------------------------ | | Kashier-Hash | Order hash [generated in hashing](/docs/direct-api/hashing). | | Content-Type | application/json | ### Operations [#operations] | Operation | Collection method | | -------------- | ----------------------------------------------- | | `GENERATE_QR` | QR code the customer scans to pay | | `INITIATE_R2P` | Request to pay, pushed to a known payer address | You do not choose the provider. InstaPay is selected with `paymentMethod.type = "instapay"`; which acquirer that resolves to is set up on your account by Kashier, not per request. ### InstaPay fields [#instapay-fields] The `paymentMethod.instapay` object: | Field | Type | Required | Description | | ---------------- | ------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `validity` | String | Yes | Validity period of the collection request. Required for both `GENERATE_QR` and `INITIATE_R2P` — without it the request fails with `request.paymentMethod?.instapay?.validity is required for IPN generate QR`. | | `payerAddress` | String | Yes for `INITIATE_R2P` | The payer's InstaPay address. Only sent when the operation is `INITIATE_R2P`. | | `tip` | Boolean | No | Set to `true` to allow a tip on the collection. | | `convenienceFee` | Number | No | Convenience fee to add to the collection. | | `refundSource` | String | No | Carried for refund flows. | ### Request [#request] ```json { "apiOperation": "INITIATE_R2P", "interactionSource": "ECOMMERCE", "order": { "reference": "", "amount": 100.00, "currency": "EGP" }, "paymentMethod": { "type": "instapay", "instapay": { "payerAddress": "payer@instapay", "validity": "30" } }, "merchantId": "MID-XXXXX-XXX" } ``` For a QR collection, send `"apiOperation": "GENERATE_QR"` and omit `paymentMethod.instapay.payerAddress`. ## Supported operations [#supported-operations] | Operation | Type | | --------------------------- | ------------ | | `GENERATE_QR` | Initiate | | `INITIATE_R2P` | Initiate | | `REFUND` | Post-payment | | `CANCEL` | Post-payment | | `TRANSACTION_FORCE_CLOSURE` | Post-payment | Tokenization, recurring and authorize/capture are not available on InstaPay. See [refunds](/docs/accept-payments/refunds) and [void](/docs/accept-payments/void) for the shared post-payment contract. ## Results and response codes [#results-and-response-codes] A successful InstaPay collection reaches you as `status: SUCCESS` with `transactionResponseCode` `00` ("Approved") — the same approved code as any other payment. Every other code is a `FAILURE` and carries the InstaPay code itself, for example `20903`. InstaPay uses its own five-digit code space, separate from the card and ISO codes in [payment reason codes](/docs/accept-payments/payment-reason-codes). Match on the exact code. | Code | Meaning | | ------- | -------------------------------------------------------------------------------- | | `00000` | Success | | `20101` | Payer Payment Address is not registered | | `20102` | Invalid validity period | | `20103` | Declined, Merchant Collection Limits Exceeded | | `20201` | Declined by Consumer | | `20202` | Declined, Payment Order Expired | | `20203` | Declined, Merchant Collection Limits Exceeded | | `20204` | Declined by Consumer bank | | `20301` | Refund Declined, Applicable Refund amount for this transaction has been exceeded | | `20302` | Transaction has already been refunded | | `20303` | Invalid Merchant Reference Number | | `20304` | Invalid Original Transaction ID | | `20305` | Refund Declined, insufficient funds | | `20306` | Refund Declined, invalid Merchant Access Permission | | `20307` | Refund Declined, Original transaction exceeded refund duration | | `20308` | Refund In Progress | | `20309` | Refund Declined | | `20310` | Refund not concluded, please inquire the refund | | `20401` | Invalid Merchant Reference Number | | `20402` | Cannot be cancelled, cancellation window has expired. Kindly use refund option | | `20501` | Invalid Merchant Reference Number | | `20502` | Declined by Consumer | | `20503` | Declined, Payment Order Expired | | `20504` | Declined, Merchant Collection Limits Exceeded | | `20901` | Invalid JSON structure or field format | | `20902` | Invalid version number | | `20903` | Invalid request signature | | `20904` | Your request cannot be processed please try again later | | `20905` | Merchant is suspended or blocked, please refer back to your bank | | `20906` | Invalid Merchant ID | | `20907` | Invalid Acquirer Merchant ID | | `20908` | Invalid Acquirer Bank ID | | `20909` | Duplicated Merchant Reference Number | | `20999` | Service currently not available, Please try again later | Only the `2xxxx` codes above are mapped. A code outside this table falls back to the general message set and can arrive as `k_default`, so treat an unknown InstaPay code as a plain failure rather than looking it up. ## Webhooks [#webhooks] InstaPay has no webhook event of its own. Kashier notifies you with the standard server webhook, where `event` is the operation — `pay`, `refund` and so on. See [webhooks](/docs/webhooks). # Meeza (/docs/accept-payments/meeza) Accept Meeza, Egypt's national card scheme, by submitting the card exactly like any other card. Send `paymentMethod.type = "card"` — never `"meeza"`. Meeza is not a payment method or a provider at Kashier; it is a card brand that Kashier detects from the card number. ## What changes [#what-changes] Nothing in your request. Everything in the [customized card form](/docs/direct-api/card-form), [3D Secure](/docs/direct-api/3d-secure), [payment sessions](/docs/accept-payments/payment-sessions) and the direct API applies unchanged — you just submit a Meeza card number. | Question | Answer | | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Which `paymentMethod.type` do I send? | `card`. `meeza` is not a valid method type. | | Are the response codes different? | No. Meeza returns the same `transactionResponseCode` dictionary and the same statuses as any card. See [payment reason codes](/docs/accept-payments/payment-reason-codes). | | Does Meeza support 3D Secure, refund and void? | Yes, the same as any other card. | | How do I identify a Meeza payment afterwards? | By its card brand: a Meeza transaction carries `cardBrand: "MEEZA"` in reporting and in the `sourceOfFunds` details on the webhook. | ## Meeza is rejected on recurring payments [#meeza-is-rejected-on-recurring-payments] A Meeza card cannot be charged on a subscription or recurring-origin payment. Kashier rejects the request with `MEEZA_NOT_ALLOWED`: | Field | Value | | ------------- | ------------------------------- | | `cause` | `cause.invalid.data` | | `explanation` | `explanation.meeza.not.allowed` | | `messageKey` | `message.meeza.not.allowed` | | `status` | failed | Meeza cards are not tokenizable for recurring the way Visa and Mastercard cards are. Use a Visa or Mastercard card for [recurring payments](/docs/accept-payments/recurring), and fall back to a one-off card payment if the customer only has a Meeza card. ## Testing [#testing] Kashier does not publish a Meeza test card. The [test cards](/docs/get-started/testing) are Visa and Mastercard only — ask [techsupport@kashier.io](mailto:techsupport@kashier.io) if you need to test a Meeza card number. # Mobile SDKs (/docs/accept-payments/mobile-sdks) Drop-in payment libraries for accepting Kashier payments in your mobile app. Pick a platform to get started. ## Flutter SDK [#flutter-sdk] Accept Apple Pay, mobile wallet, and card payments in your Flutter app from a single API call. [Read the guide](/docs/accept-payments/flutter-sdk) # Order reconciliation (/docs/accept-payments/order-reconciliation) Kashier reconciliation is the process of verifying that the electronic payment and refund transactions processed in your system match the transactions reported by Kashier. ## Retrieve order details [#retrieve-order-details] Kashier enables you to retrieve details of your orders. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------- | | TEST URL | [https://test-api.kashier.io/v3/payment/orders](https://test-api.kashier.io/v3/payment/orders) | | LIVE URL | [https://api.kashier.io/v3/payment/orders](https://api.kashier.io/v3/payment/orders) | | Method | GET | ### Parameters [#parameters] | Parameter | Type | Description | Required | | --------- | --------------- | -------------------------------------------------------------------------------------------------------- | -------- | | search | Query Parameter | Case-insensitive partial match on your merchant order id. Required — the request returns 400 without it. | true | | status | Query Parameter | Order status, for example `CAPTURED`. Uppercased before matching. | false | | startDate | Query Parameter | Lower bound on the order date. `startDate` after `endDate` returns 400. | false | | endDate | Query Parameter | Upper bound on the order date. | false | | page | Query Parameter | Page number. Default `1`. | false | | limit | Query Parameter | Page size. Default `20`. | false | ### Headers [#headers] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location 'https://test-api.kashier.io/v3/payment/orders?search={{merchantOrderId}}&status={{status}}&startDate=2025-07-21&endDate=2025-12-23' --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Reconciliation verdicts [#reconciliation-verdicts] Every entry in an order's `transactions[]` array carries two reconciliation verdicts. | Field | What it tells you | | ------------------------------- | --------------------------------------------------------------------------------- | | `reconcilation` | Kashier's verdict for this transaction against the provider/gateway. | | `merchantWebhookReconciliation` | Whether the merchant webhook for this transaction was delivered and acknowledged. | `reconcilation` has one `i`, not two. That is the spelling in the API response — match it exactly when you parse, and don't "correct" it to `reconciliation`. Note that the neighbouring `merchantWebhookReconciliation` **is** spelled with two. Both fields use the same four values: | Value | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `OK` | Reconciled and consistent. Kashier compared the two sides and they matched. | | `Failed` | Reconciliation ran but did not match, or could not be confirmed. Investigate this transaction. | | `Not_Exists` | The counterpart record was not found on the side being reconciled — for example the transaction is absent at the provider, or there is no webhook delivery record. | | `NA` | The default: **not yet reconciled**. Common on intermediate steps such as `3dsecure_verify` and `authenticate_payer`, which are not reconciled on their own. | `OK` is a verdict about *agreement between two records*, not about the money. A transaction that failed at the provider and is correctly recorded as failed on both sides is also `reconcilation: "OK"`. To learn the outcome, read the transaction's own `status` (`SUCCESS`, `FAILURE`, `PENDING`, …) and `transactionResponseCode`. Never treat `OK` as a payment confirmation. A transaction that Kashier declined on its own before it ever reached the acquirer carries `kashierBlocked: true`. Those transactions are excluded from the reconciliation jobs by design, so expect them to sit at `NA` permanently. ### Response structure [#response-structure] ```json { "status": "SUCCESS", "message": "Orders retrieved successfully", "data": [ { "_id": "68235607d26af80012b574cf", "order": { "amount": 11179.51, "currency": "EGP", "callbackURL": "https://checkouts.kashier.io/en/payment-request/68235534b463b10012cb893c" }, "status": "CAPTURED", "isLockedForPaymentProcessing": false, "provider": "mpgs", "paymentAgreement": "regular", "providerReconcilationResults": [], "merchantId": "MID-123-123", "merchantDetails": { "storeName": "TEST-Demo", "MCC": "1111", "isLive": true, "businessIndustry": "Airline", "createdUserId": "5f8d0d55b54764421b7156c3", "businessEmail": "demo@example.com", "email": "merchant@example.com", "businessContactInfo": { "addressLine1": "cairo", "addressLine2": "nasr city", "governorate": "EG-C", "zipcode": "12345", "fax": "", "hotlineNumber": "123456", "landlineNumber": "123456" }, "accountType": "PF", "subMerchantName": "Demo", "merchantIdentifier": "MID-123-123", "tradingName": "Demo", "merchantIdentifierBM": "1550000000", "businessAddress": "cairo nasr city", "apiKey": "YOUR_TEST_API_KEY", "merchantId": "MID-123-123", "webhook": { "MID": "MID-123-123", "apiKey": "YOUR_PAYMENT_API_KEY", "isEnabled": true, "url": "http://webhook_url.com" }, "isTrustedMerchant": false, "enableNotEnrolled3DSCaptcha": false }, "date": "2025-05-13T14:24:07.190Z", "orderReference": "TEST-ORD-193392644", "lastModifiedDate": "2025-05-13T14:24:11.277Z", "sessionId": "68235607d26af80012b574c9", "orderId": "3e9b9e27-9900-4b17-982e-763f6ffd106b", "merchantOrderId": "68235534b463b10012cb893c", "totalRefundedAmount": 0, "totalCapturedAmount": 11179.51, "totalAuthorizedAmount": 11179.51, "providerVersion": "68", "method": "card", "metaData": { "kashierOriginType": "paymentLink", "kashierOriginDetails": { "name": "page with reqular item", "id": "PL-2435857566", "customerName": "سلمى حسن عبدالله حسن" }, "termsAndConditions": { "time": -3, "ip": "197.37.152.242", "userAgent": { "browser": "Firefox", "version": "138.0" } }, "kashier payment UI version": "V3", "referral url": "https://checkouts.kashier.io/", "merchantWebhook": "http://webhook_url.com" }, "sourceOfFunds": { "cardInfo": { "maskedCard": "512345******2346", "cardBrand": "Mastercard", "cardHolderName": "John Doe", "cardDataToken": "95d75151-8daa-4c89-ad19-2666f2dbec52", "ccvToken": "476a148b-0482-468e-a25d-87687c20d1cb", "expiryYear": "25", "expiryMonth": "06", "storedOnFile": "NOT_STORED", "save": false, "agreement": null } }, "paymentOrigin": "PL-2435857566", "paymentType": "paymentLink", "apiKeyId": "YOUR_API_KEY_ID", "apiKeyName": "your-key-name", "paymentChannel": "online | e-commerce", "originalAmount": "11179.51", "authorizationAmount": "11179.51", "installmentAmountPerMonth": "0", "originDetails": { "name": "page with reqular item", "id": "PL-2435857566", "customerName": "سلمى حسن عبدالله حسن" }, "serverWebhook": "", "transactions": [ { "feeDetails": { "transaction": 0, "processing": 0 }, "reconcilation": "NA", "merchantWebhookReconciliation": "NA", "isCurrentlyReconciled": false, "isPF": true, "_id": "68235607d26af80012b574d0", "operation": "3dsecure_verify", "gateWayOperation": "INITIATE_AUTHENTICATION", "transactionId": "TX-24358575398", "amount": 11179.51, "currency": "EGP", "status": "SUCCESS", "feeTrxAmount": 0, "metaData": { "userAgent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0" }, "apmTraceId": "8ab33a4f6ebf919d28fdcf1ede7ff9b0", "requestDate": "2025-05-13T14:24:07.194Z", "responseDate": "2025-05-13T14:24:07.971Z", "transactionResponseCode": "AUTHENTICATION_IN_PROGRESS", "transactionResponseMessage": { "en": "Authentication in progress", "ar": "المصادقة قيد التقدم" } }, { "feeDetails": { "transaction": 0, "processing": 0 }, "reconcilation": "NA", "merchantWebhookReconciliation": "NA", "isCurrentlyReconciled": false, "isPF": true, "_id": "68235609d26af80012b574e3", "operation": "authenticate_payer", "gateWayOperation": "AUTHENTICATE_PAYER", "transactionId": "TX-24358575399", "status": "SUCCESS", "amount": 11179.51, "currency": "EGP", "feeTrxAmount": 0, "requestDate": "2025-05-13T14:24:09.604Z", "targetedTransaction": "TX-24358575398", "targetedTransactionOperation": "3dsecure_verify", "apmTraceId": "50e5e86fa5fdd1a5e177a415b0355e98", "responseDate": "2025-05-13T14:24:10.267Z", "transactionResponseCode": "APPROVED", "transactionResponseMessage": { "en": "Approved", "ar": "تمت الموافقة" } }, { "feeDetails": { "transaction": 0, "processing": 0 }, "reconcilation": "OK", "merchantWebhookReconciliation": "Failed", "isCurrentlyReconciled": false, "isPF": true, "_id": "6823560ad26af80012b574e8", "operation": "pay", "gateWayOperation": "PAY", "transactionId": "TX-24358575400", "status": "SUCCESS", "amount": 11179.51, "currency": "EGP", "feeTrxAmount": 0, "requestDate": "2025-05-13T14:24:10.283Z", "targetedTransaction": "TX-24358575398", "targetedTransactionOperation": "3dsecure_verify", "bankSettlementDate": "2025-05-13T14:24:11.267Z", "bankValueDate": "2025-05-14T07:00:00.000Z", "gatewayTransactionReference": "228001", "gatewayTransactionUniqueKey": "228001#3e9b9e27-9900-4b17-982e-763f6ffd106b", "responseDate": "2025-05-13T14:24:11.267Z", "settlementDate": "2025-05-13T14:24:11.267Z", "transactionResponseCode": "00", "transactionResponseMessage": { "en": "Approved", "ar": "تمت الموافقة" } } ], "apiOperation": null, "interChangeRate": "ON_US", "createdAt": "2025-05-13T14:24:07.201Z", "updatedAt": "2025-05-13T14:24:11.919Z", "__v": 0 }, { "_id": "68235370940bd50012e4e450", "order": { "amount": 11179.51, "currency": "EGP", "callbackURL": "https://checkouts.kashier.io/en/payment-request/68221102b463b10012cb80fb" }, "status": "CAPTURED", "isLockedForPaymentProcessing": false, "provider": "mpgs", "paymentAgreement": "regular", "providerReconcilationResults": [], "merchantId": "MID-123-123", "merchantDetails": { "storeName": "TEST-Demo", "MCC": "1111", "isLive": true, "businessIndustry": "Airline", "createdUserId": "5f8d0d55b54764421b7156c3", "businessEmail": "demo@example.com", "email": "merchant@example.com", "businessContactInfo": { "addressLine1": "cairo", "addressLine2": "nasr city", "governorate": "EG-C", "zipcode": "12345", "fax": "", "hotlineNumber": "123456", "landlineNumber": "123456" }, "accountType": "PF", "subMerchantName": "Demo", "merchantIdentifier": "MID-123-123", "tradingName": "Demo", "merchantIdentifierBM": "1550000000", "businessAddress": "cairo nasr city", "apiKey": "YOUR_TEST_API_KEY", "merchantId": "MID-123-123", "webhook": { "MID": "MID-123-123", "apiKey": "YOUR_PAYMENT_API_KEY", "isEnabled": true, "url": "http://webhook_url.com" }, "isTrustedMerchant": false, "enableNotEnrolled3DSCaptcha": false }, "date": "2025-05-13T14:13:04.956Z", "orderReference": "TEST-ORD-193392643", "lastModifiedDate": "2025-05-13T14:13:09.603Z", "sessionId": "68235370940bd50012e4e44a", "orderId": "d4240d56-49b7-44ca-a12d-e46ab1f22ca0", "merchantOrderId": "68221102b463b10012cb80fb", "totalRefundedAmount": 0, "totalCapturedAmount": 11179.51, "totalAuthorizedAmount": 11179.51, "providerVersion": "68", "method": "card", "metaData": { "kashierOriginType": "paymentLink", "kashierOriginDetails": { "name": "page with reqular item", "id": "PL-2435857563", "customerName": "سلمى حسن عبدالله حسن" }, "termsAndConditions": { "time": -3, "ip": "197.37.152.242", "userAgent": { "browser": "Firefox", "version": "138.0" } }, "kashier payment UI version": "V3", "referral url": "https://checkouts.kashier.io/", "merchantWebhook": "http://webhook_url.com" }, "sourceOfFunds": { "cardInfo": { "maskedCard": "512345******2346", "cardBrand": "Mastercard", "cardHolderName": "John Doe", "cardDataToken": "c9024ffb-bb25-4426-b793-954d89f6ff2b", "ccvToken": "c29a5430-7077-41f2-832a-36ced1f1caf2", "expiryYear": "25", "expiryMonth": "06", "storedOnFile": "NOT_STORED", "save": false, "agreement": null } }, "paymentOrigin": "PL-2435857563", "paymentType": "paymentLink", "apiKeyId": "YOUR_API_KEY_ID", "apiKeyName": "your-key-name", "paymentChannel": "online | e-commerce", "originalAmount": "11179.51", "authorizationAmount": "11179.51", "installmentAmountPerMonth": "0", "originDetails": { "name": "page with reqular item", "id": "PL-2435857563", "customerName": "سلمى حسن عبدالله حسن" }, "serverWebhook": "", "transactions": [ { "feeDetails": { "transaction": 0, "processing": 0 }, "reconcilation": "NA", "merchantWebhookReconciliation": "NA", "isCurrentlyReconciled": false, "isPF": true, "_id": "68235370940bd50012e4e451", "operation": "3dsecure_verify", "gateWayOperation": "INITIATE_AUTHENTICATION", "transactionId": "TX-24358575395", "amount": 11179.51, "currency": "EGP", "status": "SUCCESS", "feeTrxAmount": 0, "metaData": { "userAgent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0" }, "apmTraceId": "2ba18c663f73da63d44ba2bda730e382", "requestDate": "2025-05-13T14:13:04.961Z", "responseDate": "2025-05-13T14:13:06.005Z", "transactionResponseCode": "AUTHENTICATION_IN_PROGRESS", "transactionResponseMessage": { "en": "Authentication in progress", "ar": "المصادقة قيد التقدم" } }, { "feeDetails": { "transaction": 0, "processing": 0 }, "reconcilation": "NA", "merchantWebhookReconciliation": "NA", "isCurrentlyReconciled": false, "isPF": true, "_id": "68235373940bd50012e4e464", "operation": "authenticate_payer", "gateWayOperation": "AUTHENTICATE_PAYER", "transactionId": "TX-24358575396", "status": "SUCCESS", "amount": 11179.51, "currency": "EGP", "feeTrxAmount": 0, "requestDate": "2025-05-13T14:13:07.839Z", "targetedTransaction": "TX-24358575395", "targetedTransactionOperation": "3dsecure_verify", "apmTraceId": "91cbdb5e9d8ae1c70c4c9aebc9ede652", "responseDate": "2025-05-13T14:13:08.572Z", "transactionResponseCode": "APPROVED", "transactionResponseMessage": { "en": "Approved", "ar": "تمت الموافقة" } }, { "feeDetails": { "transaction": 0, "processing": 0 }, "reconcilation": "OK", "merchantWebhookReconciliation": "Failed", "isCurrentlyReconciled": false, "isPF": true, "_id": "68235374940bd50012e4e469", "operation": "pay", "gateWayOperation": "PAY", "transactionId": "TX-24358575397", "status": "SUCCESS", "amount": 11179.51, "currency": "EGP", "feeTrxAmount": 0, "requestDate": "2025-05-13T14:13:08.585Z", "targetedTransaction": "TX-24358575395", "targetedTransactionOperation": "3dsecure_verify", "bankSettlementDate": "2025-05-13T14:13:09.591Z", "bankValueDate": "2025-05-14T07:00:00.000Z", "gatewayTransactionReference": "226991", "gatewayTransactionUniqueKey": "226991#d4240d56-49b7-44ca-a12d-e46ab1f22ca0", "responseDate": "2025-05-13T14:13:09.591Z", "settlementDate": "2025-05-13T14:13:09.591Z", "transactionResponseCode": "00", "transactionResponseMessage": { "en": "Approved", "ar": "تمت الموافقة" } } ], "apiOperation": null, "interChangeRate": "ON_US", "createdAt": "2025-05-13T14:13:04.970Z", "updatedAt": "2025-05-13T14:13:10.210Z", "__v": 0 } ], "pagination": { "total": 2, "page": 1, "limit": 20, "pages": 1 } } ``` # Payment links (/docs/accept-payments/payment-links) Create and share payment links your customers can open and pay — no integration required. This API lets you: * **Create customer-specific links** — one link per customer. * **Create links in bulk** — generate links for many customers at once. * **Use currency conversion** — create links in a foreign "virtual" currency (e.g. `USD_VIRTUAL`) that settles in EGP; the customer pays the EGP equivalent at the applicable exchange rate. Requires the currency conversion feature to be enabled on your account. ## Get all payment links [#get-all-payment-links] | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/payment-link](https://test-api.kashier.io/v2/payment-link) | | LIVE-URL | [https://api.kashier.io/v2/payment-link](https://api.kashier.io/v2/payment-link) | | Method | GET | Full parameter and response reference → [List payment links](/docs/api-reference/payment-links/listPaymentLinks). ### Parameters [#parameters] | Parameter | Type | Description | | ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | currency | Query Parameter | Filter based on currency (example: EGP). If the currency conversion feature is enabled on your account, you can also filter by a virtual currency view code (example: `USD_VIRTUAL`) to get payment links created in that foreign currency. | | state | Query Parameter | Payment link state. | | startDueDate | Query Parameter | Start due date. | | endDueDate | Query Parameter | End due date. | | startAmountRange | Query Parameter | Start amount range. | | endAmountRange | Query Parameter | End amount range. | | startDate | Query Parameter | Start date. | | endDate | Query Parameter | End date. | | paymentStatus | Query Parameter | Payment state (example: PAID, UNPAID, OVERDUE, EXPIRED). | | paymentType | Query Parameter | Payment type (example: FIXED\_AMOUNT, ITEMIZED). | | search | Query Parameter | Search for a payment link by its id (example: PL-2348668602). | | page | Query Parameter | Current page number (used for pagination). | | limit | Query Parameter | Number of items per page (used for pagination). | ### Headers [#headers] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location 'https://test-api.kashier.io/v2/payment-link? currency=EGP&state=&startAmountRange=&endAmountRange=&paymentStatus=paid&search=&page=1&limit=10' --header 'Authorization: YOUR_TEST_SECRET_KEY' --header 'Accept: application/json' ``` Full parameter and response reference → [Get all payment links](/docs/api-reference/payment-links/listPaymentLinks). The virtual currency fields (virtualAmount, virtualCurrency, etc.) and exchangeRateServiceAvailable only apply to [currency conversion](#currency-conversion) payment links. ## Get payment link details [#get-payment-link-details] | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/payment-link/:paymentLinkId](https://test-api.kashier.io/v2/payment-link/:paymentLinkId) | | LIVE-URL | [https://api.kashier.io/v2/payment-link/:paymentLinkId](https://api.kashier.io/v2/payment-link/:paymentLinkId) | | Method | GET | Full parameter and response reference → [Get payment link details](/docs/api-reference/payment-links/getPaymentLink). ### Headers [#headers-1] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location 'https://test-api.kashier.io/v2/payment-link/:paymentLinkId' --header 'Authorization: YOUR_TEST_SECRET_KEY' --header 'Accept: application/json' ``` Full parameter and response reference → [Get payment link details](/docs/api-reference/payment-links/getPaymentLink). The virtual currency fields are null unless the link was created with a [currency conversion](#currency-conversion) currency. ## Create a payment link [#create-a-payment-link] | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/payment-link](https://test-api.kashier.io/v2/payment-link) | | LIVE-URL | [https://api.kashier.io/v2/payment-link](https://api.kashier.io/v2/payment-link) | | Method | POST | Full parameter and response reference → [Create a payment link](/docs/api-reference/payment-links/createPaymentLink). ### Headers [#headers-2] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location 'https://test-api.kashier.io/v2/payment-link' --header 'Content-Type: application/json' --header 'Authorization: YOUR_TEST_SECRET_KEY' --data '{ "customer":{ "name":"John Doe" }, "totalAmount": 200, "description": "", "isManualCapture": false, "paymentType": "simple", "currency": "EGP", "state": "submitted", "extraFees": [ { "name": "VAT", "flatFee": 0, "rate": 5 }, { "name": "annualFee", "flatFee": 500, "rate": 0 } ], "dueDate":"2027-12-27T21:59:00.000Z", "isSuspendedPayment": false, "referenceId":"123ss456" }' ``` Full parameter and response reference → [Create a payment link](/docs/api-reference/payment-links/createPaymentLink). ### Currency conversion [#currency-conversion] Set currency to a virtual currency code (`USD_VIRTUAL`, `EUR_VIRTUAL`, `GBP_VIRTUAL`, `SAR_VIRTUAL`, `AED_VIRTUAL`) with totalAmount expressed in that currency to collect payment in a foreign currency while settling in EGP. Requires the currency conversion feature — contact Kashier's backoffice/support to enable it. Without it, a virtual currency returns a 400: "Currency conversion feature is not enabled." ```bash curl --location 'https://test-api.kashier.io/v2/payment-link' --header 'Content-Type: application/json' --header 'Authorization: YOUR_TEST_SECRET_KEY' --data '{ "customer":{ "name":"John Doe" }, "totalAmount": 40, "description": "", "isManualCapture": false, "paymentType": "simple", "currency": "USD_VIRTUAL", "state": "submitted", "extraFees": [], "isSuspendedPayment": false, "referenceId":"123ss456" }' ``` ## Payment links bulk upload [#payment-links-bulk-upload] Bulk uploading payment links consists of two steps: 1. Upload the data for review using the /import endpoint. 2. Save the data if it is valid using the correlationId returned from the /import step. * If errors are found in the data, they will be returned for correction. * If validation passes, the API returns a correlationId. * The correlationId serves as a reference to your validated data. ### Uploading the sheet for review [#uploading-the-sheet-for-review] First, you need to upload the Excel sheet for review: | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/paymentRequest/import?currency=EGP](https://test-api.kashier.io/v2/paymentRequest/import?currency=EGP) | | LIVE-URL | [https://api.kashier.io/v2/paymentRequest/import?currency=EGP](https://api.kashier.io/v2/paymentRequest/import?currency=EGP) | | Method | POST | Replace currency=EGP with a virtual currency code (e.g. currency=USD\_VIRTUAL) to bulk-upload payment links in a foreign currency — see [currency conversion](#currency-conversion). This requires the currency conversion feature to be enabled on your account. #### Headers [#headers-3] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl 'https://test-api.kashier.io/v2/paymentRequest/import?currency=EGP' -X POST -H 'Authorization: YOUR_TEST_SECRET_KEY' -H 'Access-Control-Allow-Origin: *' -H 'Content-Type: multipart/form-data; boundary=----geckoformboundary9c673decdbea595725978e0df26aa91b' --data-binary $'------geckoformboundary9c673decdbea595725978e0df26aa91b Content-Disposition: form-data; name="file"; filename="payment-link-temp-updated.xlsx" Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet ------geckoformboundary9c673decdbea595725978e0df26aa91b-- ' ``` The **Content-Type** in the request header must be set to multipart/form-data, since you are uploading an Excel sheet in the request body. #### Response structure [#response-structure] ```json { "body": { "paymentRequests": [ { "isSuspendedPayment": false, "merchantId": "MID-XXXXX-XXX", "storeName": "TEST-Demo", "invoiceReferenceId": "Abc_123456", "paymentType": "professional", "totalAmount": "1102.50", "availableAmountForRefund": "0.00", "totalAmountWithoutFees": "1050.00", "description": "", "creationDate": "2025-08-26T10:01:58.991Z", "dueDate": null, "invoiceItems": [ { "description": "product 1 name", "quantity": "3", "unitPrice": "100.00", "subTotal": "300.00" }, { "description": "product 2 name", "quantity": "5", "unitPrice": "150.00", "subTotal": "750.00" } ], "customerName": "Ahmed Mohamed", "extraFees": [ { "name": "Fee1 Name", "flatFee": 0, "rate": 2 }, { "name": "Fee 2 name", "flatFee": 10, "rate": 0 }, { "name": "vat", "flatFee": 0, "rate": 5 } ], "currency": "EGP", "state": "submitted", "paymentStatus": "unpaid", "paymentRequestId": "INV-24358575125", "paymentLinkId": "PL-24358575125", "merchantInfo": { "storeName": "TEST-Demo" }, "createdByUserId": "5f8d0d55b54764421b7156c3", "isPaymentLink": true, "isBulkCreated": true, "referenceId": "Abc_123456", "labels": [ "Label 1", " Label 2", " Label 3" ], "errors": null } ], "correlationId": "508cf034-447a-4a14-8154-09b50c14a6c6" }, "message": "Imported successfully" } ``` ### Saving the uploaded payment links [#saving-the-uploaded-payment-links] If the uploaded data passes validation during the review step, you can save it to the database using the correlationId returned in the /import response. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save](https://test-api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save) | | LIVE-URL | [https://api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save](https://api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save) | | Method | POST | Full parameter and response reference → [Save bulk-uploaded payment links](/docs/api-reference/payment-links/saveImportedPaymentLinks). #### Headers [#headers-4] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | The request carries no body — the `correlationId` from the import step identifies the validated rows, and `operation=save` says what to do with them. ```bash curl 'https://test-api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId&operation=save' -X POST -H 'Accept: application/json, text/plain, */*' -H 'Authorization: YOUR_TEST_SECRET_KEY' ``` Full parameter and response reference → [Save the uploaded payment links](/docs/api-reference/payment-links/saveImportedPaymentLinks). ## Update a payment link [#update-a-payment-link] | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/payment-link/paymentLinkId](https://test-api.kashier.io/v2/payment-link/paymentLinkId) | | LIVE-URL | [https://api.kashier.io/v2/payment-link/paymentLinkId](https://api.kashier.io/v2/payment-link/paymentLinkId) | | Method | PUT | Full parameter and response reference → [Update payment link](/docs/api-reference/payment-links/updatePaymentLink). ### Headers [#headers-5] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location --request PUT 'https://test-api.kashier.io/v2/payment-link/{{paymentLinkId}}' --header 'Authorization: YOUR_TEST_SECRET_KEY' --header 'Content-Type: application/json' --data '{ "paymentLink":{ "customer":{ "name":"John doe" }, "totalAmount": 200, "description": "", "isManualCapture": false, "paymentType": "simple", "currency": "EGP", "state": "submitted", "extraFees": [ { "name": "tax", "flatFee": 0, "rate": 5 }, { "name": "taxen", "flatFee": 0, "rate": 10 } ] } }' ``` ## Delete a payment link [#delete-a-payment-link] | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/payment-link/paymentLinkId](https://test-api.kashier.io/v2/payment-link/paymentLinkId) | | LIVE-URL | [https://api.kashier.io/v2/payment-link/paymentLinkId](https://api.kashier.io/v2/payment-link/paymentLinkId) | | Method | DELETE | Full parameter and response reference → [Delete payment link](/docs/api-reference/payment-links/deletePaymentLink). ### Headers [#headers-6] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location --request DELETE 'https://test-api.kashier.io/v2/payment-link/{{paymentLinkId}}' --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ## Share a payment link [#share-a-payment-link] | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/payment-link/share](https://test-api.kashier.io/v2/payment-link/share) | | LIVE-URL | [https://api.kashier.io/v2/payment-link/share](https://api.kashier.io/v2/payment-link/share) | | Method | POST | Full parameter and response reference → [Share payment link](/docs/api-reference/payment-links/sharePaymentLink). ### Headers [#headers-7] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location 'https://test-api.kashier.io/v2/payment-link/share' --header 'Authorization: YOUR_TEST_SECRET_KEY' --header 'Content-Type: application/json' --data-raw '{ "operation": "email",// sms, email "urlIdentifier": "PL-ABC123DEF456", "key": "customer@example.com" }' ``` ### Body parameters [#body-parameters] | Key | Required | Description | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `operation` | yes | `email` or `phone` — how the link is delivered. | | `key` | yes | The email address or phone number to send it to. | | `urlIdentifier` | yes | The payment link's identifier, in the `PL-…` form returned by [Create a payment link](#create-a-payment-link) — not the internal `_id`. | | `countryCode` | no | Country code for `phone`, in `+XXX` form (for example `+20`). | Replace both `urlIdentifier` and `key` below before sending — `PL-ABC123DEF456` is a placeholder and will be rejected, and a valid link plus a real address genuinely emails or texts the recipient. Full parameter and response reference → [Share a payment link](/docs/api-reference/payment-links/sharePaymentLink). ## Export payment links [#export-payment-links] | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/payment-link/export](https://test-api.kashier.io/v2/payment-link/export) | | LIVE-URL | [https://api.kashier.io/v2/payment-link/export](https://api.kashier.io/v2/payment-link/export) | | Method | POST | Full parameter and response reference → [Export payment links](/docs/api-reference/payment-links/exportPaymentLinks). This endpoint does not stream a file back. It queues the export and emails it — the response only acknowledges that the email was sent. Requires the payment-link export permission on your key. ### Query parameters [#query-parameters] The export takes the same filters as [Get all payment links](#get-all-payment-links), narrowing which links end up in the file, plus: | Key | Description | | ----------- | ------------------------------------- | | `email` | Address to send the export to. | | `branchIds` | Comma-separated branch IDs to export. | The export takes no request body — the filters above are the whole request. ### Headers [#headers-8] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location --request POST 'https://test-api.kashier.io/v2/payment-link/export?limit=20&page=1&sortType=-1&paymentType=all¤cy=EGP' --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` Full parameter and response reference → [Export payment links](/docs/api-reference/payment-links/exportPaymentLinks). ## Mark as paid [#mark-as-paid] | Endpoint | Value | | -------- | ------------------------------------------------------------------------------ | | TEST-URL | [https://test-fep.kashier.io/v3/orders](https://test-fep.kashier.io/v3/orders) | | LIVE-URL | [https://fep.kashier.io/v3/orders](https://fep.kashier.io/v3/orders) | | Method | POST | Full parameter and response reference → [Pay with a card token, or mark a payment link as paid](/docs/api-reference/tokens/payWithToken). ### Headers [#headers-9] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location 'https://test-fep.kashier.io/v3/orders' --header 'Authorization: YOUR_TEST_SECRET_KEY' --header 'Content-Type: application/json' --data ' { "order":{ "reference":"67c59e14e626cf0012fdb9de", "amount":65, "currency":"EGP" }, "origin":{ "id":"PL-XXXXXXXXXX" }, "apiOperation":"PAY", "interactionSource":"ECOMMERCE", "paymentMethod":{ "type":"cash" }, "metaData":{ "kashierOriginType":"paymentLink", "kashierOriginDetails":{ "name":"", "id":"PL-XXXXXXXXXX", "customerName":"bashar", "createdUserId":"5f8d0d55b54764421b7156c3" } } }' ``` Every identifier in this body has to come from your own account — the `PL-…` link ID in `origin.id`, the order reference, and the `createdUserId` of the dashboard user recording the payment. There's no placeholder set that produces a valid request, so a runnable panel here would only ever return a `400` — "This request is invalid". Fill the values above into the curl and run it against your own test account instead. Note that a successful call records a real cash payment against the link and moves it to `paid`. ### Body description [#body-description] | Key | Description | | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | order.reference | A unique identifier for the order (e.g., "6773fa351652132156d37f7b"). | | order.amount | The total amount of the order (e.g., 200). | | order.currency | The currency in which the transaction is processed (e.g., 'EGP' for Egyptian Pounds). | | origin.id | A unique identifier for the origin of the payment request, such as an invoice number (e.g., "INV-2334424102"). | | apiOperation | Specifies the operation being performed. 'PAY' indicates that this is a payment request. | | interactionSource | Defines where the payment is initiated from. "ECOMMERCE" indicates that the payment is processed online via an e-commerce platform. | | paymentMethod | The payment method type (e.g., "cash"). | | metaData.kashierOriginType | The type of origin associated with the payment (e.g., "paymentLink"). | | metaData.kashierOriginDetails.id | The unique identifier of the origin (e.g., "PL-2435857557"). | | metaData.kashierOriginDetails.customerName | The name of the customer associated with this payment (e.g., "John doe"). | | metaData.kashierOriginDetails.createdUserId | The user ID of the person who created this transaction (e.g., "5f8d0d55b54764421b7156c3"). | ### Response structure [#response-structure-1] ```json { "response":{ "apiOperation":"PAY", "operation":"pay", "currency":"EGP", "result":"SUCCESS", "status":"SUCCESS", "authorizationNumber":"", "authentication":{ }, "paymentMethod":{ "type":"cash" }, "metaData":{ "kashierOriginType":"paymentLink", "kashierOriginDetails":{ "name":"", "id":"PL-XXXXXXXXXX", "customerName":"John", "createdUserId":"5f8d0d55b54764421b7156c3" }, "termsAndConditions":{ "ip":"197.37.122.82" }, "kashier_user":{ "id":"5f8d0d55b54764421b7156c3", "fullName":"John Doe", "email":"john.doe@example.com", "selectedMID":"MID-XXXXX-XXX" }, "merchantWebhook":"https://your-website.com/kashier-webhook" }, "origin":{ "name":"", "id":"PL-XXXXXXXXXX", "customerName":"John Doe", "createdUserId":"5f8d0d55b54764421b7156c3" }, "reconciliation":{ "webhookUrl":"", "redirect":false }, "merchantId":"MID-XXXXX-XXX", "order":{ "amount":65, "currency":"EGP", "systemOrderId":"d2e68974-5a5c-48eb-8f5f-6bd54ccbe830", "reference":"67c59e14e626cf0012fdb9de" }, "amount":65, "totalRefundedAmount":0, "totalCapturedAmount":65, "totalAuthorizedAmount":65, "method":"cash", "creationDate":"2025-03-03T14:20:27.492Z", "orderId":"d2e68974-5a5c-48eb-8f5f-6bd54ccbe830", "provider":"cash", "merchantOrderId":"67c59e14e626cf0012fdb9de", "orderReference":"TEST-ORD-193387945", "paymentType":"paymentLink", "interactionSource":"Online", "device":{ "ipAddress":"197.37.122.82" }, "transactionId":"TX-24358575179", "transactionResponseCode":"00", "transactionResponseMessage":{ "en":"Approved", "ar":"تمت الموافقة" } }, "messages":{ "en":"Approved", "ar":"تمت الموافقة" }, "status":"SUCCESS", "showCaptcha":false } ``` # Payment reason codes (/docs/accept-payments/payment-reason-codes) Every payment outcome carries a `transactionResponseCode` and a bilingual `transactionResponseMessage` object (`{ en, ar }`). Both appear on webhook payloads, in [order reconciliation](/docs/accept-payments/order-reconciliation) responses, and in transaction responses — a declined authorisation still returns HTTP 200 with the reason in the code. Branch on both `status` and `transactionResponseCode`. Matching is exact and case-sensitive: compare against the codes exactly as written here, including lowercase `k_*`. Blank Arabic cells mean no Arabic message is published for that code. ## ISO numeric codes [#iso-numeric-codes] Each numeric code is stored and returned individually. There are no range values — you will receive `47`, never `46-50`. | transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar | | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `0` | Success | | | `00` | Approved | تمت الموافقة | | `01` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة | | `02` | Refer to card issuer's special conditions | يرجى الرجوع إلى جهة إصدار البطاقة | | `03` | Invalid merchant | تاجر غير صالح | | `04` | Pick-up lost card detected | إلتقط البطاقة ، تم الإبلاغ عن سرقتها أو ضياعها | | `05` | Do not honor, please retry or contact your issuer bank. | غير مقبول، يرجى إعادة المحاولة أو الاتصال بالبنك المصدر بالبطاقة | | `06` | Error | خطأ في الدفع | | `07` | Pick-up card, special condition | إلتقط البطاقة ، تم الإبلاغ عن سرقتها أو ضياعها | | `08` | Honour with identification | مقبول مع تحديد الهوية | | `09` | Request in progress | طلب قيد التقدم | | `10` | Approved for partial amount | تمت الموافقة على المبلغ الجزئي | | `11` | Approved (VIP) | موافقة كبار الشخصيات | | `12` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة | | `13` | Invalid amount, please contact the merchant | مبلغ غير صحيح ، يرجى الاتصال بالتاجر | | `14` | Invalid card information. | بيانات البطاقة غير صحيحة. | | `15` | No such issuer | لا يوجد مثل هذا المصدر | | `16` | Approved | عملية مقبولة | | `17` | Customer cancellation | إلغاء العميل | | `18` | Customer dispute | نزاع العميل | | `19` | Re-enter transaction | أعد إدخال المعاملة | | `20` | Invalid response | استجابة غير صالحة | | `21` | No action taken | لم يتم اتخاذ أي إجراء | | `22` | Suspected malfunction | عطل مشتبه به | | `23` | Unacceptable transaction fee | رسوم المعاملات غير مقبولة | | `24` | File update not supported by receiver | تم رفض الدفع | | `25` | Unable to locate record on file | تم رفض الدفع | | `26` | Duplicate file update record, old record replaced | تم رفض الدفع | | `27` | File update field edit error | تم رفض الدفع | | `28` | File update file locked out | تم رفض الدفع | | `29` | File update not successful, contact acquirer | تم رفض الدفع | | `30` | Format error | خطأ في التنسيق | | `31` | Bank not supported by switch | البنك غير مدعوم | | `32` | Completed partially | اكتمل جزئيا | | `33` | Expired card | بطاقة منتهية الصلاحية | | `34` | Suspected fraud | يشتبه في الاحتيال | | `35` | Card acceptor contact acquirer | عملية مرفوضة | | `36` | Restricted card, please contact your bank. | بطاقة محظورة ، يرجى الاتصال بالبنك المصدر للبطاقة. | | `37` | Card acceptor call acquirer security | عملية مرفوضة | | `38` | Allowable PIN tries exceeded | تم تجاوز عدد محاولات PIN المسموح بها | | `39` | No credit account | لا يوجد حساب ائتمان | | `40` | Requested function not supported | الوظيفة المطلوبة غير مدعومة | | `41` | Lost card | البطاقة مفقودة | | `42` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة | | `43` | Stolen card, pick-up | بطاقة مسروقة | | `44` | No investment account | لا يوجد حساب استثماري | | `45` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. | | `46` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. | | `47` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. | | `48` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. | | `49` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. | | `50` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. | | `51` | Insufficient funds | رصيد البطاقة غير كاف | | `52` | No checking account | لا يوجد حساب جاري | | `53` | No savings account | لا يوجد حساب توفير | | `54` | Expired card | بطاقة منتهية الصلاحية | | `55` | Declined - Wrong PIN Entered by Card Holder | مرفوض - إدخال خاطئ لرقم التعريف الشخصي بواسطة حامل البطاقة | | `56` | No card record | لا يوجد سجل للبطاقة | | `57` | Online transactions are not permitted to this card, please contact the card issuer. | المعاملات عبر الإنترنت غير مسموح بها لهذه البطاقة ، يرجى الاتصال بالبنك مصدر البطاقة. | | `58` | Online transactions are not permitted to this card, please contact the card issuer. | المعاملات عبر الإنترنت غير مسموح بها لهذه البطاقة ، يرجى الاتصال بالبنك مصدر البطاقة. | | `59` | Suspected fraud | تم رفض الدفع بسبب إشتباه في إحتيال | | `60` | Card acceptor contact acquirer | تم رفض الدفع | | `61` | Exceeds withdrawal amount limit | تم رفض الدفع بسبب تجاوز حدود المبلغ المسموح للدفع | | `62` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة | | `63` | The three-digit CVV or CVC security code … was incorrect. | | | `64` | Original amount incorrect | المبلغ الأصلي غير صحيح | | `65` | Exceeds withdrawal frequency limit | تم إجتياز حد تردد السحب | | `66` | Card acceptor call acquirer's security department | التقط البطاقة من الجهاز | | `67` | Hard capture (requires that card be picked up at ATM) | الالتقاط الثابت (يتطلب استلام البطاقة من جهاز الصراف الآلي) | | `68` | Response received too late | رد متأخر | | `69` | Reserved for ISO use | عملية مرفوضة | | `70` | Reserved for ISO use | عملية مرفوضة | | `71` | Reserved for ISO use | عملية مرفوضة | | `72` | Reserved for ISO use | عملية مرفوضة | | `73` | Reserved for ISO use | عملية مرفوضة | | `74` | Reserved for ISO use | عملية مرفوضة | | `75` | Allowable number of PIN tries exceeded | تم تجاوز العدد المسموح به من محاولات PIN | | `76` | Reserved for private use | عملية مرفوضة | | `77` | Reserved for private use | عملية مرفوضة | | `78` | Declined - No Account | مرفوض - لا يوجد حساب | | `79` | The card issuer suspects this payment to be fraudulent. | | | `80` | Reserved for private use | عملية مرفوضة | | `81` | Reserved for private use | عملية مرفوضة | | `82` | CVV Validation Error | | | `83` | CVV Validation Error | | | `84` | Reserved for private use | عملية مرفوضة | | `85` | Reserved for private use | عملية مرفوضة | | `86` | Reserved for private use | عملية مرفوضة | | `87` | Reserved for private use | عملية مرفوضة | | `88` | Reserved for private use | عملية مرفوضة | | `89` | Reserved for private use | عملية مرفوضة | | `90` | Cutoff is in process (switch ending a day's business and starting the next. Transaction can be sent again in a few minutes) | قطع قيد التنفيذ | | `91` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة | | `92` | Financial institution or intermediate network facility cannot be found for routing | لا يمكن العثور على مؤسسة مالية أو مرفق شبكة وسيطة للتوجيه | | `93` | Transaction cannot be completed. Violation of law | لا يمكن إتمام العملية بسبب انتهاك للقانون | | `94` | Duplicate transmission | انتقال مكرر | | `95` | Reconcile error | خطأ في التصالح | | `96` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة | | `97` | Declined - CVV MisMatch | عملية مرفوضة، رمز الأمان غير مطابق. | | `98` | Declined | عملية مرفوضة | | `99` | Declined | عملية مرفوضة | ## Authentication and 3DS codes [#authentication-and-3ds-codes] Returned by the 3D Secure and authentication step. | transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar | | ----------------------- | --------------------------------------------------- | ------------------------------------ | | `AA` | UNSPECIFIED FAILURE | فشل غير محدد | | `BB` | ACQUIRER SYSTEM ERROR | خطأ في نظام البنك | | `CC` | UNKNOWN | غير معلوم | | `U` | Authentication is not available | المصادقة غير متوفرة | | `X` | Authentication is not available | المصادقة غير متوفرة | | `P` | Authentication failed, please check your card info. | المصادقة فشلت، خطأ في بيانات الكارت. | | `N` | Authentication failed | المصادقة فشلت | | `S` | Invalid Signature on Authentication Response | توقيع غير صالح في استجابة المصادقة | | `I` | MPI Processing Error | MPI Processing Error | | `M` | Authentication Attempted (No CAVV) | Authentication Attempted (No CAVV) | Card enrolment and ACS outcomes can also arrive as a `transactionResponseCode`: `CARD_ENROLLED`, `CARD_NOT_ENROLLED`, `ACS_ERROR`, `ACS_SUCCESS`, `ACS_FAILED`, `AUTHENTICATION_SUCCESSFUL`, `AUTHENTICATION_ATTEMPTED`, `AUTHENTICATION_FAILED`, `CARD_ENROLLEMENT_UNSPECIFIED`, `INVALID_CARD_NUMBER`, `REJECTED_CARD`. ## Gateway codes [#gateway-codes] When a card is processed through the MPGS gateway, the gateway result word becomes the `transactionResponseCode` verbatim. `APPROVED` and `AUTHENTICATION_IN_PROGRESS` are returned on the intermediate 3DS step of a card order, so expect them in reconciliation responses. | transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar | | ------------------------------- | ---------------------------------------------------------------- | ------------------------------- | | `gw_0` | Declined. | | | `ABORTED` | Transaction aborted by payer | | | `ACQUIRER_SYSTEM_ERROR` | Acquirer system error occurred processing the transaction | | | `APPROVED` | Approved | | | `APPROVED_AUTO` | The transaction was automatically approved by the gateway … | | | `APPROVED_PENDING_SETTLEMENT` | Transaction Approved - pending batch settlement | | | `AUTHENTICATION_IN_PROGRESS` | Authentication in progress | المصادقة قيد التقدم | | `BALANCE_AVAILABLE` | Balance / points-redemption hints | | | `BALANCE_UNKNOWN` | Balance / points-redemption hints | | | `BLOCKED` | Your card is blocked, Kindly contact your issuer bank | | | `CANCELLED` | Transaction cancelled by payer | | | `DECLINED` | Payment was declined by issuer or payer authentication … | | | `DECLINED_AVS` | Address / CSC verification declines | | | `DECLINED_AVS_CSC` | Address / CSC verification declines | | | `DECLINED_CSC` | Address / CSC verification declines | | | `DECLINED_DO_NOT_CONTACT` | Transaction declined - do not contact issuer | | | `DECLINED_INVALID_PIN` | PIN declines | | | `DECLINED_PIN_REQUIRED` | PIN declines | | | `DECLINED_PAYMENT_PLAN` | Transaction declined due to payment plan | | | `DEFERRED_TRANSACTION_RECEIVED` | Deferred transaction received and awaiting processing | | | `DUPLICATE_BATCH` | Transaction declined due to duplicate batch | | | `EXCEEDED_RETRY_LIMIT` | Transaction retry limit exceeded | | | `EXPIRED_CARD` | Transaction declined due to expired card | | | `INSUFFICIENT_FUNDS` | Transaction declined due to insufficient funds | | | `INVALID_CSC` | Invalid card security code | | | `LOCK_FAILURE` | Order locked - another transaction is in progress for this order | | | `NO_BALANCE` | No balance available on the card | | | `NO_MATCH` | CVV Validation Error | | | `NOT_ENROLLED_3D_SECURE` | Authentication not supported. | | | `NOT_SUPPORTED` | Transaction type not supported, please refer to your bank. | | | `PARTIALLY_APPROVED` | Partially Approved transaction | | | `PENDING` | Transaction is pending | | | `REFERRED` | Transaction declined - refer to issuer | | | `SUBMITTED` | Transaction submitted without a known response | | | `SYSTEM_ERROR` | System error | | | `TIMED_OUT` | Transaction timeout | | | `UNKNOWN` | Unkown Result | | | `UNSPECIFIED_FAILURE` | unspecified failure | | ## Kashier codes [#kashier-codes] Kashier-internal outcomes covering tokenization, refunds, wallets, QR, and payouts. These keys are lowercase — an uppercase `K_4` will not match. | transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar | | ----------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------- | | `k_default` | A General Error Occured, please contact support. | حدث خطأ عام ، يرجى الاتصال بالدعم. | | `k_0` | Invalid detoken parameter. | رمز غير صالح | | `k_1` | Token not found. | لم يتم العثور على الرمز | | `k_2` | Token expired after 10 minutes of inactivity. | انتهت صلاحية الرمز بعد 10 دقائق من عدم النشاط. | | `k_3` | card security code does not exist. | رمز أمان البطاقة غير موجود. | | `k_4` | Request to pay message send successfully. | | | `k_5` | The Mobile number is not registered on any provider. | | | `k_6` | Approved | | | `k_7` | Order not paid | | | `k_8` | Transaction refunded successfully | | | `k_9` | Invalid refund body, please check your inputs | | | `k_10` | Transaction already refunded | | | `k_11` | QR code generated successfully | | | `k_12` | Payment not completed, OTP page did not displayed to the user | | | `k_13` | Transaction not found on provider | | | `k_14` | Expired | | | `k_15` | Self-transfer is not allowed | | | `k_16` | Internal account credit failed | | | `k_17` | Recipient credit not found | | | `k_101` | Transaction In Progress | | | `REFUND_DISABLED` | The bank has disabled refund operations for this merchant. Please contact your bank to enable refunds. | | `k_default` is also the fallback message for any code Kashier does not recognise, so it can accompany a code that is not in these tables. Two further codes reach you without a dictionary entry of their own, and both carry the `k_default` message: `k_timeout`, returned when the gateway sends no response, and `k_risk`, a retry-guard decline. Treat `k_timeout` as an unknown outcome and reconcile it against the order — never read it as a success or a failure. ## Installment and OTP codes [#installment-and-otp-codes] Returned by the buy-now-pay-later and OTP flows, such as valU and Souhoola. | transactionResponseCode | Meaning | | ----------------------- | ---------------------------------------------- | | `ERR_00` | Technical Failure, Please restart the process. | | `ERR_CST_01` | Not enrolled | | `ERR_CST_02` | No limit or inactive | | `ERR_CST_03` | Amount exceeds limit | | `ERR_CST_04` | Not eligible | | `ERR_DTA_01` | Invalid request | | `ERR_DTA_02` | OTP not found | | `ERR_DTA_03` | OTP expired | | `ERR_DTA_04` | Invalid OTP | | `ERR_DTA_05` | Already verified | | `ERR_DTA_06` | Max OTP attempts | | `ERR_DTA_07` | Invalid downpayment | | `ERR_DTA_08` | Invalid financed amount | | `ERR_DTA_09` | Wrong payment plan | | `ERR_ORD_01` | Order not found | | `ERR_ORD_02` | Duplicate order. | | `ERR_PRD_01` | Product not found | | `ERR_PRD_02` | Product Already refunded | | `ERR_VDR_01` | Vendor not found | | `ERR_VDR_02` | Store details not found | | `ERR_VDR_03` | Vendor not allowed to redeem discount | # Payment sessions (/docs/accept-payments/payment-sessions) Kashier's payment sessions make transactions more secure and efficient. With a single API call, you can create payments without exposing sensitive data in query strings, and session history tracking lets you monitor every action in real time. This page walks through creating a session, sending the customer to pay, and confirming the result. ## Step 1: Create a payment session [#step-1-create-a-payment-session] | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v3/payment/sessions](https://test-api.kashier.io/v3/payment/sessions) | | LIVE-URL | [https://api.kashier.io/v3/payment/sessions](https://api.kashier.io/v3/payment/sessions) | | Method | POST | ```bash curl --location 'https://test-api.kashier.io/v3/payment/sessions' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'api-key: YOUR_TEST_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "expireAt": "2030-01-28T17:27:32.359Z", "maxFailureAttempts": 3, "paymentType": "credit", "amount": "100.00", "currency": "EGP", "order": "8f64saf6sa4", "merchantRedirect": "https://your-website.com/redirect", "display": "en", "type": "one-time", "allowedMethods": "card,wallet", "redirectMethod": null, "iframeBackgroundColor": "#FFFFFF", "metaData": { "customKey": "customValue", "displayNotes": {"key": "value"} }, "merchantId": "MID-XXXX-XXX", "failureRedirect": false, "brandColor": "#FF5733", "defaultMethod": "card", "description": "Payment for order ORD123456", "manualCapture": false, "customer": { "email": "john@example.com", "reference": "894321" }, "saveCard": "optional", "retrieveSavedCard": true, "interactionSource": "ECOMMERCE", "enable3DS": true, "serverWebhook": "https://your_webhook_url", "notes": "Special handling required" }' ``` `merchantId` is required and the panel cannot guess it. Save your test **merchant ID** next to your keys in the banner above and it is substituted into the `MID-XXXX-XXXX` placeholder automatically. Until you do, the panel treats the placeholder as an unfilled parameter and keeps Send disabled rather than firing a request the API is certain to reject. Change `order` as well. It is your own reference for the payment and Kashier rejects a duplicate order reference for the same merchant (`ERR_ORD_02`), so leaving the placeholder in place collides with everyone else who left it alone. ### Headers [#headers] | Key | Description | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | | api-key | You can obtain your `api-key` from the merchant dashboard under the **Integrations** section. Please note that the API key is different for the *live* and *test* environments, so make sure to use the correct one for each. | Full parameter and response reference → [Create payment session](/docs/api-reference/payment-sessions/createPaymentSession). ## Step 2: Send the customer to pay [#step-2-send-the-customer-to-pay] sessionUrl is the URL that will be used to redirect the customer to the payment page. You can use it as the src attribute in a link or ` ``` This embed flow was formerly documented as the "Payment UI Builder". The legacy documentation page hosted an interactive test builder that generated a checkout from the fields below. Use the [create payment session](#step-1-create-a-payment-session) call with these fields to build your checkout. #### Merchant details [#merchant-details] * **Merchant ID** * **API key** * **Secret key** #### Order details [#order-details] * **Order ID** — unique order reference between merchant and Kashier * **Amount** * **Currency** #### Payment method [#payment-method] Select the payment methods to enable: * Card * Wallet * Bank installment * Buy now pay later #### Redirect settings [#redirect-settings] * **Redirect URL** * **Redirect method** — Get or Post * **Redirect failure** — True or False #### Display settings [#display-settings] * **Display language** — English or Arabic * **Display mode** — Test or Live * **Brand color** — e.g. `#00bcbc` ### Getting the result out of an embedded checkout [#getting-the-result-out-of-an-embedded-checkout] When you embed rather than redirect, the customer never leaves your page, so there is no redirect landing you can read the outcome from. The checkout talks back to the page that hosts it with `window.postMessage`. Add a `message` listener on your page and switch on `e.data.message`. What you receive depends on how you embedded it. **If you embed with Kashier's checkout script**, the script sits between the checkout and your page and re-emits a small, clean set: | `e.data.message` | Emitted when | What you do | | ---------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `iframeLoaded` | The checkout is loaded and its pay button is ready. | Drop your own loading state. | | `paymentSuccess` | The payment succeeded. | Show your success state. | | `iframeHide` | The customer closed or dismissed the checkout. | Remove your overlay and re-enable the page. | | `urlRedirection` | The checkout is ready to return to your redirect URL. | Nothing. The script redirects the top window for you — it builds an auto-submitting form when `redirectMethod` is `post`, and otherwise replaces the location. | **If you render `sessionUrl` in your own ` ``` 3. JavaScript snippet for handling Kashier's response and opening the 3DS frame: ```js const authentication = data.response && data.response.authentication; if (authentication && authentication.redirectUrl) { // Preferred: point the 3DS iframe at the ACS redirect URL $('#3ds_iframe').attr('src', authentication.redirectUrl); $('#3ds_iframe').addClass('show').removeClass('hide'); } else if (authentication && authentication.redirectHtml) { // Fallback: render the self-submitting HTML form Kashier returns const frame = document.getElementById('3ds_iframe'); frame.srcdoc = authentication.redirectHtml; $('#3ds_iframe').addClass('show').removeClass('hide'); } ``` 4. JavaScript snippet for handling the 3D Secure response: ```js function iFrameMessageListener(e) { var iFrameMessage = e.data; console.log(iFrameMessage); // 3D Secure Return Cases if (iFrameMessage.message == 'merchantStoreRedirect' && iFrameMessage.params) { // Remove 3ds_iframe $('#3ds_iframe').addClass('hide').removeClass('show'); switch (iFrameMessage.params.status) { case 'SERVER_ERROR' || 'INVALID_REQUEST': // Failed Authentication or other errors // Write your code in case of failed payment break; case 'SUCCESS': // Succeeded in payment authentication if ( iFrameMessage.params.response && iFrameMessage.params.response.card.result == 'SUCCESS' ) { let parsedRedirectUrl = iFrameMessage.redirectUrl.replace(/&/g, '&'); // Write your code in case of successful payment } else { // Write your code in case of failed payment } break; default: displayMessage(false, __frameMessage('error.please.check.card.info')); // Write your code in case of failed payment break; } } } if (window.addEventListener) { addEventListener('message', iFrameMessageListener, false); } else { attachEvent('onmessage', iFrameMessageListener); } ``` # Customized card form (/docs/direct-api/card-form) ## Getting started [#getting-started] To integrate directly with Kashier APIs and save the card as a card token, you'll need to go through the following steps. Your system should save the returned token with your customer profile for future use in direct payments. A `cardToken` is included in the response (at `response.paymentMethod.card.cardToken`) **only** when the transaction status is `SUCCESS` or `PENDING` **and** the request carried `paymentMethod.card.save` or `paymentMethod.card.agreement` — or the card was already stored on file (`storedOnFile: "STORED"`). On any other outcome the token is suppressed, so don't build on it always being present. ## Step 1: Create order hash [#step-1-create-order-hash] Order hash generation uses HMAC SHA256. See [the process of generating the hash](/docs/direct-api/hashing#hashing). ## Step 2: Pay [#step-2-pay] The request is a POST request that allows you to pay directly. | Endpoint | Value | | -------- | ---------------------------------------- | | TEST-URL | `https://test-fep.kashier.io/v3/orders/` | | LIVE-URL | `https://fep.kashier.io/v3/orders/` | | Method | POST | ```bash curl -X 'POST' 'https://test-fep.kashier.io/v3/orders/' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Kashier-Hash: your_generated_hash' \ -d '{ "apiOperation": "PAY", "paymentMethod": { "type": "CARD", "card": { "save": true, "expiry": { "month": "05", "year": "26" }, "number": "XXXXXXXXXXXX0001", "nameOnCard": "TEST", "securityCode": "100" } }, "order": { "reference": "", "amount": "1", "currency": "EGP", "description": "" }, "interactionSource": "ECOMMERCE", "reconciliation": { "webhookUrl": "https://your-call-back-url.com", "merchantRedirect": "https://your-call-back-url.com", "redirect": true }, "customer": { "reference": "24", "firstName": "ghanem", "lastName": "ghanem", "email": "ghanem@example.com" }, "merchantId": "MID-2-670" }' ``` `order.reference` is your own merchant order ID, and `interactionSource` must be `ECOMMERCE`, `MOTO`, or `RECURRING`. See the field reference below for the rest. ## Body parameters [#body-parameters] | Parameter | Description | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | merchantId (String) | Merchant account number or merchant ID | | number (string) | Card PAN number | | nameOnCard (string) | Cardholder name | | year (string) | Card expiry year | | month (string) | Card expiry month | | securityCode (string) | Card Verification Value/Code | | save (bool) | If you want to save the card as a token to pay with later. When the transaction succeeds, a token is included in the response body and webhook response. [Webhook](/docs/webhooks/payloads) | | enable3DS (bool) | Whether to run 3D Secure for this payment. Send it at `paymentMethod.card.enable3DS` — **not** directly under `paymentMethod`. When absent it is treated as `true`, so an `ECOMMERCE` card pay runs 3DS and returns `AUTHENTICATION_INITIATED` rather than a final status. | | apiOperation (string) | Should be: PAY | | connectedAccount.merchantId (string) | Make payments on behalf of your [connected account](/docs/accept-payments/connected-accounts) by sending the Sub Merchant/Connected Account merchant ID in the JSON body: `"connectedAccount": { "merchantId": "MID-452-644" }`. The value must start with `MID-`. | | order (JSON object) | Contains the order details `{"reference": "1","amount": "1", "currency": "EGP", "description": "" }`
- currency (String): order currency (ISO: "EGP", "USD", "GBP" "EUR")
- amount (string): order amount
- reference (string): order identifier
- description (string): order description | | merchantRedirect (string) | merchantRedirect should be URI encoded | | redirect (bool) | Redirect to the merchantRedirect URL after the transaction has been completed, whether it is unsuccessful or successful | | serverWebhook (string) | Pass an endpoint to receive server-to-server notifications. Setting this up on your application is as easy as creating a new page that accepts unauthenticated POST requests. The event object is sent as JSON in the request body. [Webhook](/docs/webhooks/payloads) | | interactionSource (string) | Mandatory; must be `MOTO`, `RECURRING`, or `ECOMMERCE` based on the business case | | customer (JSON object) | Contains the customer details `{"reference": "24","firstName": "ghanem","lastName": "ghanem","email": "ghanem@example.com"}`
- reference (String): customer reference ID to associate it with the card (required)
- firstName (string): first name of the customer (optional)
- lastName (string): last name of the customer (optional)
- email (string): email of the customer (optional)
- mobilePhone (string): mobile number of the customer (optional)
- nationalId (string): national ID of the customer (optional) | | metaData (JSON object) | Additional data you can send and receive via webhooks and responses |
The customer object is required in case of card `save:true` . ## Response structure at MOTO with NON-3DS [#response-structure-at-moto-with-non-3ds] ```json { "response": { "apiOperation": "PAY", "operation": "pay", "currency": "EGP", "result": "SUCCESS", "status": "CAPTURED", "authenticationStatus": "AUTHENTICATION_NOT_IN_EFFECT", "amount": 100, "creationTime": "2023-10-16T12:00:49.746Z", "lastUpdatedTime": "2023-10-16T12:00:50.137Z", "merchantCurrency": "EGP", "reference": "1697457648576", "totalAuthorizedAmount": 100, "totalCapturedAmount": 100, "totalDisbursedAmount": 0, "totalRefundedAmount": 0, "authentication": {}, "paymentMethod": { "type": "CARD", "card": { "cardBrand": "Mastercard", "storedOnFile": "TO_BE_STORED", "number": "512345******2346", "nameOnCard": "Mohamed Khaled", "expiry": { "month": "12", "year": "25" }, "cardToken": "9d8332cb-6195-40ea-aed0-86c3aa60fbaa" } }, "metaData": { "customerName": "Noura Mosaad", "merchantWebhook": "https://your-website.com/paymentWebhook", "redirect": true }, "customer": { "reference": "01163550555" }, "timestamp": "2022-12-15T13:54:01.606Z", "reconciliation": { "webhookUrl": "https://your-call-back-url.com", "merchantRedirect": "https://your-call-back-url.com?paymentStatus=SUCCESS&cardDataToken=9d8332cb-6195-40ea-aed0-86c3aa60fbaa&maskedCard=512345******2346&merchantOrderId=1697457648576&orderId=27a86389-83be-4107-b51d-33ad767078a2&cardBrand=Mastercard&orderReference=TEST-ORD-96353&transactionId=TX-2498912113&amount=100¤cy=EGP&mode=test&signature=dde46b3a9b4fc05478f6a35dd62f82a2a3c0b6ace73997dfdcebda0abd7b8cc3", "redirect": true }, "merchantId": "MID-123-123", "order": { "amount": 100, "currency": "EGP", "callbackURL": "https://your-call-back-url.com", "systemOrderId": "27a86389-83be-4107-b51d-33ad767078a2" }, "merchantRedirectUrl": "https://your-call-back-url.com?paymentStatus=SUCCESS&cardDataToken=9d8332cb-6195-40ea-aed0-86c3aa60fbaa&maskedCard=512345******2346&merchantOrderId=1697457648576&orderId=27a86389-83be-4107-b51d-33ad767078a2&cardBrand=Mastercard&orderReference=TEST-ORD-96353&transactionId=TX-2498912113&amount=100¤cy=EGP&mode=test&signature=dde46b3a9b4fc05478f6a35dd62f82a2a3c0b6ace73997dfdcebda0abd7b8cc3", "apiKeyId": "5d0003fc77c68a0018b05a6f", "method": "card", "creationDate": "2023-10-16T15:00:48.946Z", "orderId": "27a86389-83be-4107-b51d-33ad767078a2", "merchantOrderId": "1697457648576", "orderReference": "TEST-ORD-96353", "paymentType": "ext-default", "interactionSource": "MOTO", "transactionId": "TX-2498912113", "transactionResponseCode": "00", "transactionResponseMessage": { "en": "Approved", "ar": "تمت الموافقة" } }, "messages": { "en": "Approved", "ar": "تمت الموافقة" }, "status": "SUCCESS", "showCaptcha": false } ``` You can receive the transaction response in the [webhook](/docs/webhooks/payloads). ## Response structure at Ecommerce with 3DS [#response-structure-at-ecommerce-with-3ds] ```json { "response": { "apiOperation": "PAY", "operation": "3dsecure_verify", "currency": "EGP", "result": "SUCCESS", "status": "AUTHENTICATION_INITIATED", "authenticationStatus": "AUTHENTICATION_AVAILABLE", "creationTime": "2023-10-16T11:53:51.094Z", "lastUpdatedTime": "2023-10-16T11:53:51.059Z", "totalAuthorizedAmount": 0, "totalCapturedAmount": 0, "totalRefundedAmount": 0, "authentication": { "channel": "PAYER_BROWSER", "purpose": "PAYMENT_TRANSACTION", "redirectHtml": "
", "version": "3DS2", "redirectUrl": "https://checkout.kashier.io/3dsRedirect/bdcab401-d3eb-4cb1-80a7-6bc59f595961?methodUrl=https://mtf.gateway.mastercard.com/acs/mastercard/v2/method&methodPostData=eyJ0aHJlZURTTWV0aG9kTm90aWZpY2F0aW9uVVJMIjoiaHR0cHM6Ly9tdGYuZ2F0ZXdheS5tYXN0ZXJjYXJkLmNvbS9jYWxsYmFja0ludGVyZmFjZS9nYXRld2F5Lzk2MDc0YmNhNmNlNGU1ZWZlMmZhNDExM2Y5MjdkOWQ2ZGUwY2ZiOTg5NGI5ZDU0ODYwZjc1NDI0OTg1MWZhNzkiLCJ0aHJlZURTU2VydmVyVHJhbnNJRCI6IjhmY2EyMmQ2LTNjOTktNDQ2NC04YmNjLWIyMmVmNTBhMzY2ZSJ9&mode=test" }, "paymentMethod": { "type": "CARD", "card": { "cardBrand": "Mastercard", "storedOnFile": "TO_BE_STORED", "number": "512345******2346", "nameOnCard": "Mohamed Khaled", "expiry": { "month": "12", "year": "25" }, "cardToken": "9d8332cb-6195-40ea-aed0-86c3aa60fbaa" } }, "metaData": { "customerName": "Noura Mosaad", "merchantWebhook": "https://your-website.com/paymentWebhook", "redirect": true }, "customer": { "reference": "01163550555" }, "timestamp": "2022-12-15T13:54:01.606Z", "reconciliation": { "webhookUrl": "https://your-call-back-url.com", "merchantRedirect": "https://your-call-back-url.com?&signature=", "redirect": true }, "merchantId": "MID-123-123", "order": { "amount": 100, "currency": "EGP", "callbackURL": "https://your-call-back-url.com", "systemOrderId": "bdcab401-d3eb-4cb1-80a7-6bc59f595961" }, "description": "order description", "merchantRedirectUrl": "https://your-call-back-url.com?&signature=", "apiKeyId": "5d0003fc77c68a0018b05a6f", "method": "card", "creationDate": "2023-10-16T14:53:50.593Z", "orderId": "bdcab401-d3eb-4cb1-80a7-6bc59f595961", "merchantOrderId": "1697457230044", "orderReference": "TEST-ORD-96349", "paymentType": "ext-default", "interactionSource": "ECOMMERCE", "transactionId": "TX-2498912112", "transactionResponseCode": "AUTHENTICATION_IN_PROGRESS", "transactionResponseMessage": { "en": "Authentication in progress", "ar": "المصادقة قيد التقدم" } }, "messages": { "en": "Authentication in progress", "ar": "المصادقة قيد التقدم" }, "status": "SUCCESS", "showCaptcha": false } ``` After a successful response, you should redirect to `authentication.redirectUrl` to generate the 3DS page. As soon as the transaction has completed, it will redirect to `merchantRedirectUrl` if `reconciliation.redirect` was equal to true; otherwise use [3D Secure handling](/docs/direct-api/3d-secure). You can receive the transaction response after 3DS processing in the [webhook](/docs/webhooks/payloads). ## Meeza cards [#meeza-cards] Meeza (Egypt's national card scheme) is **not** a separate payment method — it's a card brand that Kashier detects from the card number and routes automatically. You submit a Meeza card exactly like any other card: `paymentMethod.type: "CARD"` with the PAN, expiry, and security code. There's no `"meeza"` value to send anywhere. Kashier identifies a Meeza card by its BIN prefix (`9` or `50`) and picks the acquiring rail for you — normally the UPG gateway online, or the ISO 8583 rail on POS. If your account has an MPGS credential provisioned, Meeza traffic is routed to MPGS instead and follows the standard MPGS card flow (3DS, auth/capture, void, refund) described above. A Meeza card routed over UPG cannot be used for a subscription-origin (recurring) payment — the request fails with a `MEEZA_NOT_ALLOWED` error, because Meeza-over-UPG cards aren't tokenizable for recurring the way MPGS Visa/Mastercard cards are. See [Recurring payments](/docs/accept-payments/recurring) for the tokenizable card flow. Meeza transactions use the same response/reason-code dictionary, statuses, and webhook shape as any other card — there's no Meeza-specific code table. ## Pay with token [#pay-with-token] To make a payment transaction with a card token that has been saved, see [Pay with card token](/docs/direct-api/pay-with-token). # Delete token (/docs/direct-api/delete-token) A DELETE request is made to the "CancelToken" endpoint. A token can be removed using it. For example, an expired card, a new card, or a stolen card — it can be useful for removing the old card information. ## Hashing [#hashing] The remove hash is used to validate your order with what your customers are paying. Order hash generation uses the HMAC SHA256 crypto mechanism. You should generate the hash from your backend, and it is implemented as explained below: ```js //Copy and paste this code in your Backend let crypto = require('crypto'); function generateKashierOrderHash(order) { const mid = 'MID-123-123'; //your merchant id const reference = '1'; //your customer id to save card const secret = 'yourPaymentApiKey'; const path = `/?tokenization=${mid}.${reference}`; const hash = crypto.createHmac('sha256', secret).update(path).digest('hex'); return hash; } ``` ```php //Copy and paste this code in your Backend function generateKashierOrderHash($order){ $mid = "MID-123-123"; //your merchant id $secret = "yourPaymentApiKey"; $reference = "1"; //your customer id to save card $path = "/?tokenization=".$mid.".".$reference; $hash = hash_hmac('sha256', $path, $secret, false); return $hash; } ``` ```python #Copy and paste this code in your Backend import hmac import hashlib import binascii def generateKashierOrderHash(order): mid = "MID-123-123"; #your merchant id reference = '1'; #your customer id to save card path = '/?tokenization={}.{}'.format(mid, reference) path = bytes(path, 'utf-8') secret = "yourPaymentApiKey" secret = bytes(secret, 'utf-8') return hmac.new(secret, path, hashlib.sha256).hexdigest() ``` ```csharp //Copy and paste this code in your Backend using System.Security.Cryptography; public class Kashier { public static string create_hash(){ string mid = "MID-123-123"; //your merchant id string reference = "1"; //your customer Id string secret = "yourPaymentApiKey"; string path = "/?tokenization=" + mid + "." + reference; string message; string key; key = secret; message = path; System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); byte[] keyByte = encoding.GetBytes(key); byte[] messageBytes = encoding.GetBytes(message); HMACSHA256 hmacmd256 = new HMACSHA256(keyByte); byte[] hashmessage = hmacmd256.ComputeHash(messageBytes); return ByteToString(hashmessage).ToLower(); } public static string ByteToString(byte[] buff){ string sbinary = ""; for (int i = 0; i < buff.Length; i++){ sbinary += buff[i].ToString("X2"); // hex format } return (sbinary); } } ``` For creating the hash, use only the parameters mentioned in the snippet above. Don't add extra parameters in the hash creation. ## Remove token [#remove-token] In case you are still in the development phase, you will need to call our API using the following testing endpoint API URL. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------ | | TEST-URL | `https://test-fep.kashier.io/v3/cards/tokens/:token?customerReference=yourcustomerReference&merchantId=merchantId` | | LIVE-URL | `https://fep.kashier.io/v3/cards/tokens/:token?customerReference=yourcustomerReference&merchantId=merchantId` | | Method | DELETE | ## Headers [#headers] | Key | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------------- | | Authorization | Your Secret Key. | | Kashier-Hash (String) | HMAC SHA256 of `/?tokenization={mid}.{customerReference}`, keyed with your Payment API Key — see [Hashing](#hashing) above. | ```bash curl -X 'DELETE' 'https://test-fep.kashier.io/v3/cards/tokens/:token?customerReference={{customerReference}}' \ -H 'Authorization: your_secretKey' \ -H 'Kashier-Hash: your_generated_hash' \ -H 'accept: application/json' ``` Full parameter and response reference → [Delete token](/docs/api-reference/tokens/deleteToken). # Request hashing (/docs/direct-api/hashing) ## Hashing [#hashing] Kashier uses hashing to ensure that the Payment UI and responses shared between your application and Kashier over the network have not been tampered with. We use SHA256 hashing to ensure the safety of transaction data. The order hash is used to validate your order against what your customers are paying. Order hash generation uses the HMAC SHA256 cryptographic mechanism. You should generate the hash from your backend, as explained below. You can obtain your [API key](/docs/get-started/api-keys) from the [Dashboard](https://merchant.kashier.io/en/dashboard). Both the order hash and the response signature below are generated with your **Payment API Key** — not your Secret Key. If the "API key" vs. "secret key" vs. "MID" terminology gets confusing, see the [credentials-at-a-glance table](/docs/get-started/api-keys#credentials-at-a-glance) for how each term maps to its actual header or field name. ```js // Copy and paste this code into your backend let crypto = require('crypto'); function generateKashierOrderHash(order) { const mid = 'MID-123-123'; // Your merchant ID const CustomerReference = ''; // Required when save, cardToken, or agreement is sent const amount = order.amount; // e.g., 22.00 const currency = order.currency; // e.g., "EGP" const orderId = order.merchantOrderId; // e.g., 99 const apiKey = 'yourApiKey'; const path = `/?payment=${mid}.${orderId}.${amount}.${currency}${CustomerReference ? '.' + CustomerReference : ''}`; const hash = crypto.createHmac('sha256', apiKey).update(path).digest('hex'); return hash; } // The result hash for /?payment=mid-0-1.99.20.EGP with key 11111 // should be 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4bec ``` ```php // Copy and paste this code into your backend function generateKashierOrderHash($order) { $mid = "MID-123-123"; // Your merchant ID $amount = $order->amount; // e.g., 100 $currency = $order->currency; // e.g., "EGP" $orderId = $order->merchantOrderId; // e.g., 99 $apiKey = "yourApiKey"; $CustomerReference = ""; // Required when save, cardToken, or agreement is sent $path = "/?payment=".$mid.".".$orderId.".".$amount.".".$currency; if (!empty($CustomerReference)) { $path .= ".".$CustomerReference; } return hash_hmac('sha256', $path, $apiKey, false); } // The result hash for /?payment=mid-0-1.99.20.EGP with key 11111 // should be 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4bec ``` ```python # Copy and paste this code into your backend import hmac import hashlib def generateKashierOrderHash(order): mid = "MID-123-123" # Your merchant ID amount = order['amount'] # e.g., 100 currency = order['currency'] # e.g., "EGP" orderId = order['merchantOrderId'] # e.g., 99 CustomerReference = "" # Required when save, cardToken, or agreement is sent path = f"/?payment={mid}.{orderId}.{amount}.{currency}" if CustomerReference: path += f".{CustomerReference}" apiKey = "yourApiKey" return hmac.new(apiKey.encode('utf-8'), path.encode('utf-8'), hashlib.sha256).hexdigest() # The result hash for /?payment=mid-0-1.99.20.EGP with secret 11111 # should be 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4bec ``` ```csharp // Copy and paste this code into your backend using System; using System.Security.Cryptography; public class Kashier { public static string CreateHash() { string mid = "mid-0-1"; // Merchant ID from the comment example string amount = "20"; // Amount from the comment example string currency = "EGP"; string orderId = "99"; // Order ID from the comment example string CustomerReference = ""; // Required when save, cardToken, or agreement is sent string apiKey = "11111"; // Payment API Key from the comment example string path = $"/?payment={mid}.{orderId}.{amount}.{currency}"; if (!string.IsNullOrEmpty(CustomerReference)) { path += $".{CustomerReference}"; } using (var hmac = new HMACSHA256(System.Text.Encoding.ASCII.GetBytes(apiKey))) { byte[] hash = hmac.ComputeHash(System.Text.Encoding.ASCII.GetBytes(path)); return BitConverter.ToString(hash).Replace("-", "").ToLower(); } } } // The result hash for /?payment=mid-0-1.99.20.EGP with key 11111 // should be: 606a8a1307d64caf4e2e9bb724738f115a8972c27eccb2a8acd9194c357e4bec ``` For creating the hash, use only the parameters mentioned in the snippet above. Do not add extra parameters during hash creation. ### Which string to sign [#which-string-to-sign] The algorithm is always the same — HMAC-SHA256, lowercase hex digest, keyed with your **Payment API Key**. Only the signed string changes with the operation. Parts are joined with a literal `.` and are **not** URL-encoded. | Operation | String to sign | | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | Payment / checkout, no tokenization | `/?payment={mid}.{reference}.{amount}.{currency}` | | Payment **with** tokenization (`card.save`, `card.cardToken`, or `card.agreement`) | `/?payment={mid}.{reference}.{amount}.{currency}.{customerReference}` | | Token CRUD — [retrieve](/docs/direct-api/retrieve-tokens) / [delete](/docs/direct-api/delete-token) a saved card | `/?tokenization={mid}.{customerReference}` | | 3D Secure pay (resuming an in-flight 3DS order) | `/?payment={mid}.{orderId}` | | Payment session create (Apple Pay SDK / sessions) | `/?payment={mid}.{reference}.{amount}.{currency}` — append `.{customerReference}` when you send one | Operations that are **not** in this table are not hashed. In particular there is no published signed string for `PUT /v3/orders/:orderId` — the [refund](/docs/accept-payments/refunds), [void](/docs/accept-payments/void) and [capture](/docs/accept-payments/authorize-capture) route — which authenticates with your `Authorization` secret key instead. Don't try to reuse the payment string for it. Here `{mid}` is your merchant ID, `{reference}` is your `order.reference` (your merchant order ID), and `{orderId}` on the 3DS-resume row is Kashier's **system** order ID for the order being resumed — not your own reference. On the session-create row the amount is coerced to a number before signing, so `20.00` signs as `20`. Append `.{customerReference}` to the signed string whenever the request carries `paymentMethod.card.save`, `paymentMethod.card.cardToken`, or `paymentMethod.card.agreement`. For those requests Kashier accepts only the customer-reference variant — hash without it and you get `403 INVALID_HASH_CHECK`. For a plain payment with none of those fields, both variants are accepted. The Payment API Key is mode-scoped at the hash layer. A test key validates only on the `test-` hosts and a live key only on the live hosts, so a mode mismatch fails the hash even when your formula is correct. This is the most common cause of an "invalid hash" error — check the key before you check the string. ## Signature [#signature] Once the transaction is processed, Kashier creates a signature with response parameters and sends it in the redirection along with other parameters. You need to validate the signature appended to the redirection URL. For validating the signature in the response, use the function explained below. Kashier signs a **fixed, ordered list** of parameters — not whatever happens to be on the URL. Rebuild that exact string yourself rather than iterating the query string, so that an extra, missing, or re-ordered parameter can't silently break your check. A parameter that is absent from the redirect is signed as the literal string `null`. ```js const crypto = require('crypto'); // Copy and paste this code into your backend function validateSignature(query, secret) { const body = `paymentStatus=${query.paymentStatus}` + `&cardDataToken=${query.cardDataToken}` + `&maskedCard=${query.maskedCard}` + `&merchantOrderId=${query.merchantOrderId}` + `&orderId=${query.orderId}` + `&cardBrand=${query.cardBrand}` + `&orderReference=${query.orderReference}` + `&transactionId=${query.transactionId}` + `&amount=${query.amount}` + `¤cy=${query.currency}`; const signature = crypto.createHmac('sha256', secret).update(body).digest('hex'); return signature === query.signature; } ``` ```php ``` ```python # Copy and paste this code in your backend import hmac import hashlib FIELDS = [ "paymentStatus", "cardDataToken", "maskedCard", "merchantOrderId", "orderId", "cardBrand", "orderReference", "transactionId", "amount", "currency", ] def validateSignature(request, secret): body = "&".join(f"{field}={request.get(field, 'null')}" for field in FIELDS) signature = hmac.new( secret.encode("utf-8"), body.encode("utf-8"), hashlib.sha256 ).hexdigest() return "success" if hmac.compare_digest(signature, request.get("signature", "")) else "failure" ``` ```csharp using System; using System.Security.Cryptography; using System.Text; using System.Web.Mvc; public class WebhookController : Controller { private static readonly string[] Fields = { "paymentStatus", "cardDataToken", "maskedCard", "merchantOrderId", "orderId", "cardBrand", "orderReference", "transactionId", "amount", "currency" }; [HttpPost] public JsonResult ValidateSignature() { string secret = "Payment API Key"; string signature = Request.QueryString["signature"]; if (string.IsNullOrEmpty(signature)) { return Json(new { success = false, message = "Missing signature." }); } var parts = new StringBuilder(); foreach (string field in Fields) { if (parts.Length > 0) parts.Append("&"); parts.Append($"{field}={Request.QueryString[field] ?? "null"}"); } byte[] keyBytes = Encoding.ASCII.GetBytes(secret); byte[] messageBytes = Encoding.ASCII.GetBytes(parts.ToString()); using (var hmac = new HMACSHA256(keyBytes)) { byte[] hashMessage = hmac.ComputeHash(messageBytes); string computedSignature = ByteToString(hashMessage).ToLower(); if (computedSignature == signature.ToLower()) { return Json(new { success = true, message = "Signature validated successfully." }); } else { return Json(new { success = false, message = "Signature validation failed." }); } } } public static string ByteToString(byte[] buff) { StringBuilder sbinary = new StringBuilder(); for (int i = 0; i < buff.Length; i++) { sbinary.Append(buff[i].ToString("X2")); // hex format } return sbinary.ToString(); } } ``` Handle the webhook: when a payment is successful, Kashier sends a payment webhook event to the webhook URL that you provide. Learn more about using [webhooks](/docs/webhooks). ## Redirect [#redirect] Kashier sends the response of the transaction to the redirect URL provided by the merchant. The URL in the format `https://your_website.com/redirect` can be sent in the data-merchantRedirect parameter in the Payment UI. Below are the parameters you can include in the Payment UI solution's redirect URL. ### Parameters [#parameters] | Parameter | Description | | --------------- | ------------------------------------------------------------------------------------------------- | | paymentStatus | Status of the transaction: either SUCCESS or FAILURE | | cardDataToken | Your shopper's card token for future and recurring payments. | | maskedCard | The masked card of your customer. | | merchantOrderId | Your order identifier used in [order reconciliation](/docs/accept-payments/order-reconciliation). | | orderId | Kashier's system identifier for the order. | | cardBrand | The card brand (sometimes called a card network or association). | | orderReference | The order reference. | | transactionId | Kashier's identifier for the transaction (for example `TX-2498912113`), not your own reference. | | amount | The amount of the order. | | currency | The currency of the order. | | signature | Order signature to ensure a secure connection between your server and Kashier. | | mode | The mode of operation, either [test](/docs/get-started/api-keys#test-and-live-modes) or live. | Kashier signs a fixed, ordered list of those parameters — not whatever happens to be on the URL. Rebuild the body in exactly this order, then HMAC it with your Payment API Key: ``` paymentStatus, cardDataToken, maskedCard, merchantOrderId, orderId, cardBrand, orderReference, transactionId, amount, currency ``` `signature` and `mode` are excluded from the signed body. A parameter that is absent from the redirect is signed as the literal string `null`. [Learn how to validate the signature](/docs/webhooks) to ensure that the Payment UI and responses shared between your application and Kashier over the network have not been tampered with. You can retrieve your order details using [order reconciliation](/docs/accept-payments/order-reconciliation). If your transaction fails, you can determine the reason for the failed transaction by mapping the transactionResponseCode to the corresponding [payment reason code](/docs/accept-payments/payment-reason-codes). ## Demo [#demo] You can download and install our integration demo: * [PHP demo](https://github.com/Kashier-payments/Php-Checkout-Demo) * [Node.js demo](https://github.com/Kashier-payments/NodeJs-Checkout-Demo) # Direct API integration (/docs/direct-api) Direct API integration gives you full control over the payment experience: your own card form, token-based repeat payments, 3D Secure handling, wallets, installments, and kiosk payments. Collecting card data on your own pages changes your PCI DSS scope compared to the hosted checkout. Review your compliance requirements before choosing this path. ## The core path [#the-core-path] These three pages are the integration. Work through them in order — none of them is optional, and a card payment will not complete if you skip one: 1. **[Request hashing](/docs/direct-api/hashing)** — every request is signed, and the signature is generated on your backend with your Payment API Key. Read this first; an unsigned or wrongly-signed request fails before anything else you build gets exercised. 2. **[Customized card form](/docs/direct-api/card-form)** — collect the card and submit the payment. 3. **[3D Secure handling](/docs/direct-api/3d-secure)** — handle the authentication challenge and verify the result. Most Egyptian card transactions are challenged, so treat this as part of the happy path rather than an edge case. ## Optional add-ons [#optional-add-ons] Add these only if your flow needs them: * **Saved cards** — [pay with token](/docs/direct-api/pay-with-token), [retrieve tokens](/docs/direct-api/retrieve-tokens), and [delete token](/docs/direct-api/delete-token). * **Other payment methods** — [wallet payments](/docs/direct-api/wallet-payments) and [kiosk & cash payments](/docs/direct-api/kiosk-payments) (Basata, Aman, Cash). * **[Installment plans](/docs/direct-api/installment-plans)** — bank installments and standalone BNPL providers (valU, Octo, Souhoola, Contact, Mogo, Tru, Forsa). See [rate limits](/docs/resources/rate-limits) for the request-per-minute cap on the payment API. # Installment plans (/docs/direct-api/installment-plans) To retrieve installment plans and pay with one: ## Step 1: Get available banks [#step-1-get-available-banks] | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------ | | URL | `https://test-api.kashier.io/merchant/installments/banks/plans?mid=yourMerchantID&amount=productPrice` | | Method | GET | Whenever you are ready for production, use the following production API endpoint URL instead. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------- | | URL | `https://api.kashier.io/merchant/installments/banks/plans?mid=yourMerchantID&amount=productPrice` | | Method | GET | ```bash curl -X 'GET' 'https://test-api.kashier.io/merchant/installments/banks/plans?mid=MID-0000-000&amount=500' ``` Full parameter and response reference → [Get available banks](/docs/api-reference/installments/getInstallmentBanks). ### Query parameters [#query-parameters] | Parameter | Required | Notes | | ------------ | -------- | --------------------------------------------------------------------------------- | | `mid` | Yes | Your merchant ID. | | `amount` | Yes | The product price, used to filter out plans whose minimum the order doesn't meet. | | `clientType` | No | Sending `POS` removes QNB and Banque Misr from the returned bank list. | This step returns each bank's minimum as `minimunInstallmentAmount` — the missing second `m` is genuinely part of the wire key, not a typo in these docs. Step 2 returns the same value under a different name, `Minimum installment amount`. Handle both spellings across the two steps. `allowFawry` may still appear in this response, but as far as we can confirm nothing routes to Fawry today: the provider is implemented in Kashier's payment-object layer, yet it has no entry in the payment routing tables that back the `/v3/orders` flow, so there is no Fawry equivalent of the Basata reference-code path. Confirm with Kashier support before planning a Fawry integration or building against the flag. ### BNPL availability flags [#bnpl-availability-flags] Alongside `banks`, the response carries boolean flags showing which BNPL providers are available for the merchant, computed from real provisioning for that merchant: | Flag | Enabled when | | --------------- | ------------------------------------ | | `allowValu` | valU is provisioned for the merchant | | `allowOcto` | Octo is provisioned for the merchant | | `allowSouhoola` | Souhoola is enabled for the merchant | These BNPL providers use their own dedicated payment flow — they are **not** paid via the `installments.planId` card path described in Step 3 below. ## Step 2: Get plans for a specific bank [#step-2-get-plans-for-a-specific-bank] | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------- | | URL | `https://test-api.kashier.io/merchant/installments/plans?mid=yourMerchantID&amount=productPrice¤cy=EGP&fiId=banknSystemID` | | Method | GET | Whenever you are ready for production, use the following production API endpoint URL instead. | Endpoint | Value | | -------- | --------------------------------------------------------------------------------------------------------------------------- | | URL | `https://api.kashier.io/merchant/installments/plans?mid=yourMerchantID&amount=productPrice¤cy=EGP&fiId=banknSystemID` | | Method | GET | ```bash curl -X 'GET' 'https://test-api.kashier.io/merchant/installments/plans?mid=MID-0000-000&amount=10000¤cy=EGP&fiId=FI-13' ``` Full parameter and response reference → [Get plans for specific bank](/docs/api-reference/installments/getInstallmentPlans). ### Query parameters [#query-parameters-1] | Parameter | Required | Notes | | ----------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `mid` | Yes | Your merchant ID. | | `amount` | Yes | The product price. | | `currency` | Yes | Must be `EGP`. Any other value is rejected with a hard `400` and the cause `EGP.currency.is.required.in.installment` — bank installments are EGP-only. | | `fiId` **or** `cardBin` | Exactly one | `fiId` is the `Bank system ID` from Step 1. `cardBin` is an accepted alternative: pass the customer's card BIN and Kashier resolves the bank for you. Sending both, or neither, is rejected with `financial.institution.system.id.or.the.card.bin.is.required`. | | `clientType` | No | Sending `POS` returns a camelCase response shape that also carries `interestAmount` per plan, instead of the default `Title Case` online shape. | The response renames the Step 1 minimum: what Step 1 calls `minimunInstallmentAmount` is returned here as `Minimum installment amount`. ## Step 3: Pay with a plan [#step-3-pay-with-a-plan] Once you have a `Merchant Plan ID` (e.g. `INSPLAN-53`) from Step 2, pay with it by adding `installments.planId` to a normal card payment request. This is the same `POST /v3/orders` request used for any [card payment](/docs/direct-api/card-form) — the plan is validated before the card is charged. | Endpoint | Value | | -------- | ---------------------------------------- | | URL | `https://test-fep.kashier.io/v3/orders/` | | Method | POST | Whenever you are ready for production, use the following production API endpoint URL instead. | Endpoint | Value | | -------- | ----------------------------------- | | URL | `https://fep.kashier.io/v3/orders/` | | Method | POST | ```bash curl -X 'POST' 'https://test-fep.kashier.io/v3/orders/' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Kashier-Hash: your_generated_hash' \ -d '{ "apiOperation": "PAY", "installments": { "planId": "INSPLAN-53" }, "paymentMethod": { "type": "CARD", "card": { "number": "5484571234560000", "expiry": { "month": "05", "year": "26" }, "nameOnCard": "TEST", "securityCode": "100" } }, "order": { "reference": "PM-89451648984", "amount": "10000", "currency": "EGP", "description": "" }, "interactionSource": "ECOMMERCE", "reconciliation": { "webhookUrl": "https://your-call-back-url.com", "merchantRedirect": "https://your-call-back-url.com", "redirect": true }, "customer": { "reference": "24" }, "merchantId": "MID-0000-000" }' ``` ### Body parameters [#body-parameters] | Parameter | Description | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `installments.planId` (string) | The `Merchant Plan ID` from Step 2 (e.g. `INSPLAN-53`). Matched against the plan copy enabled for your account. Omit `installments` entirely for a regular (non-installment) card payment. | | `paymentAgreement` | Set by Kashier, not by you. Omit `installments` and the request is treated as `regular`; send `installments` and it becomes `installment`. | ### How the plan is validated [#how-the-plan-is-validated] When `installments` is present, the payment is treated as an installment agreement (as opposed to a regular card charge), and the plan is validated before the card is charged: 1. `installments.planId` must match a known master plan. This check runs in **every** mode, test included. 2. The plan must be active. 3. The card's BIN must fall inside the plan's `binRanges` (the same ranges returned as `Supported BINs` in Step 2). 4. The plan must actually be provisioned and active for your merchant account. If any check fails, the request is rejected before the card is charged, with one of the error codes below. Validate the card BIN against `Supported BINs` in your own UI before submitting, so the customer is not shown a plan their card cannot use. Only the *active*-plan and BIN-range checks are gated on live mode — checks 2 and 3 above don't run in test. The master-plan lookup in check 1 still runs, so an unknown `planId` returns `PLAN_NOT_EXIST` in test just as it does live. If your **merchant copy** of a valid plan is missing, test mode synthesizes a default copy with zero fees and `planFinancing: true`, so a test payment can succeed on a plan that would be rejected live. ### Pay-time errors [#pay-time-errors] | Code | Meaning | Fix | | ----------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | `PLAN_NOT_EXIST` | No plan matches the `planId` you sent, or your account has no copy of it. | Re-read the plan list from Step 2 and send a `Merchant Plan ID` from the current response. | | `PLAN_NOT_ACTIVE` | The plan exists but is not active. | Pick another plan, or ask Kashier support to activate it for your account. | | `INVALID_BIN_RANGE` | The card BIN is outside the plan's supported ranges. | Ask the customer for a card from the bank that owns the plan, or offer a different plan. | | `INVALID_MERCHANT_PLAN` | The plan does not belong to your merchant account. | Use only plans returned for your own `mid`. | ### Response [#response] Once validation passes, the normal card payment flow runs unchanged — including 3DS, capture, and the response shape. See [Customized card form](/docs/direct-api/card-form) for the full response structure, and [3D Secure handling](/docs/direct-api/3d-secure) for the 3DS redirect flow. You can find how much the customer will pay monthly for every plan in the `Installment amount per month` parameter of the Step 2 response. ## BNPL installment providers [#bnpl-installment-providers] Beyond bank card installments (Steps 1–3 above), Kashier supports several standalone BNPL (buy-now-pay-later) providers. Each is its own `paymentMethod.type`, with its own dedicated payment flow — none of them are paid via the `installments.planId` card path. | Provider | `paymentMethod.type` | Provider type(s) | | -------- | -------------------- | -------------------------------- | | valU | `valu` | `valu` (online), `valupos` (POS) | | Octo | `octo` | `octo` | | Souhoola | `souhoola` | `souhoola` | | Contact | `contact` | `contact`, `contact_pos` | | Mogo | `mogo` | `mogo`, `mogo_pos` | | Tru | `tru` | `tru` | | Forsa | `forsa` | `forsa`, `forsa_pos` | ### Availability [#availability] Of these, three are surfaced as [availability flags on the Step 1 banks response](#bnpl-availability-flags) — `allowValu`, `allowOcto`, and `allowSouhoola`. Mogo, Tru, Forsa, and Contact don't have a documented availability flag of their own on that response; whether one of them is usable for a given merchant depends on that merchant's own provisioning rather than a flag here. ### Contact [#contact] Contact (`paymentMethod.type: "contact"`, provider `contact` online / `contact_pos` on POS) is a BNPL provider, not a cash or kiosk method. It's initiated with `get_categories` / `validate_balance` and then progresses through tenure selection, OTP, pay, and refund (including a dedicated `otp_refund` step). # Kiosk & cash payments (/docs/direct-api/kiosk-payments) ## Overview [#overview] Kashier has three payment methods in this family, each with a different completion mechanism: | Method | `paymentMethod.type` | How it completes | | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------ | | Basata | `basata` | You generate a payment reference code; the customer pays it at a Basata outlet, and Basata notifies Kashier. | | Aman | `aman` | The customer completes an OTP-verified installment purchase through Aman. | | Cash | `cash` | Internal "mark-as-paid" bookkeeping operation — no external provider call, no real-time authorization. | Those are BNPL/installment providers, not cash or kiosk methods, even though Contact's name suggests otherwise. See [Installment plans → BNPL providers](/docs/direct-api/installment-plans#bnpl-installment-providers). ## Basata: generate payment code [#basata-generate-payment-code] Generates a payment code for the Basata payment method. | Endpoint | Value | | -------- | --------------------------------------- | | TEST-URL | `https://test-fep.kashier.io/v3/orders` | | LIVE-URL | `https://fep.kashier.io/v3/orders` | | Method | POST | ### Headers [#headers] | Endpoint | Required | Value | | ------------ | -------- | --------------------------------------------------------- | | Kashier-Hash | Yes | HMAC-SHA256 hash calculated based on the path and API key | | Content-Type | Yes | application/json | ```bash curl --location 'https://test-fep.kashier.io/v3/orders' \ --header 'Kashier-Hash: your_generated_hash' \ --header 'Content-Type: application/json' \ --data '{ "apiOperation": "GENERATE_CODE", "paymentMethod": { "type": "BASATA", "basata": { "mobileNumber": "01001001001" } }, "order": { "reference": "PM-89451648984", "amount": 10.00, "currency": "EGP" }, "interactionSource": "ECOMMERCE", "reconciliation": { "webhookUrl": "https://your-call-back-url.com", "merchantRedirect": "https://your-call-back-url.com" }, "merchantId": "MID-XXXXX-XXXX" }' ``` ### Body description [#body-description] | Parameter | Type | Required | Description | | --------------------------------- | ------ | -------- | -------------------------------------------------------------- | | apiOperation | String | Yes | Must be set to 'GENERATE\_CODE' | | paymentMethod.type | String | Yes | Must be set to 'BASATA' | | paymentMethod.basata.mobileNumber | String | Yes | Customer's mobile number | | order.reference | String | Yes | Unique order reference ID (can be generated using a timestamp) | | order.amount | Number | Yes | Transaction amount | | order.currency | String | Yes | Currency code (e.g., 'EGP') | | interactionSource | String | Yes | Source of interaction (typically 'ECOMMERCE') | | reconciliation.webhookUrl | String | Yes | URL for webhook notifications | | reconciliation.merchantRedirect | String | Yes | URL to redirect the customer after the transaction | | merchantId | String | Yes | Your merchant ID provided by Kashier | ### Response structure [#response-structure] `GENERATE_CODE` does not call Basata at all — Kashier generates the reference code and its `expireAt` itself on this leg, and the `transactionResponseCode: "00"` / `"Approved"` below is produced locally by that generator. The **order** is created with status `INITIATED`, not paid. The customer still has to take the reference code to a Basata outlet. Treat the completion webhook (`event: pay`, `status: SUCCESS`) as the authoritative "customer has paid" signal — never this response. ```json { "response": { "apiOperation": "GENERATE_CODE", "operation": "generate_code", "currency": "EGP", "result": "SUCCESS", "status": "SUCCESS", "authorizationNumber": "", "authentication": {}, "paymentMethod": { "type": "BASATA", "basata": { "mobileNumber": "01001001001", "referenceCode": "21496319", "expireAt": "2025-04-06T13:05:45.007Z" } }, "metaData": { "termsAndConditions": { "ip": "156.210.51.26" }, "merchantWebhook": "https://your-website.com/paymentWebhook" }, "reconciliation": { "webhookUrl": "https://your-call-back-url.com", "merchantRedirect": "https://your-call-back-url.com?&signature=", "redirect": false }, "merchantId": "MID-21232-888", "order": { "amount": 10, "currency": "EGP", "callbackURL": "https://your-call-back-url.com", "systemOrderId": "e5f15beb-c922-42a2-b6ba-015f5a435a20", "reference": "test12d34" }, "amount": 10, "totalRefundedAmount": 0, "totalCapturedAmount": 0, "totalAuthorizedAmount": 0, "merchantRedirectUrl": "https://your-call-back-url.com?&signature=", "apiKeyId": "64e630f515427000134fd486", "method": "basata", "creationDate": "2025-04-06T14:05:44.981Z", "orderId": "e5f15beb-c922-42a2-b6ba-015f5a435a20", "provider": "basata", "merchantOrderId": "test12d34", "orderReference": "TEST-ORD-193390011", "interactionSource": "ECOMMERCE", "device": { "ipAddress": "156.210.51.26" }, "transactionId": "TX-212328882424", "transactionResponseCode": "00", "transactionResponseMessage": { "en": "Approved" } }, "messages": { "en": "Approved" }, "status": "SUCCESS", "showCaptcha": false } ``` ### Key response fields [#key-response-fields] | Parameter | Description | | ------------------------------------------- | -------------------------------------------- | | response.result | Result of the operation ('SUCCESS' or error) | | response.paymentMethod.basata.referenceCode | The generated Basata reference code | | response.paymentMethod.basata.expireAt | Expiration time for the reference code | | response.transactionId | Unique transaction ID | | response.order.systemOrderId | System-generated order ID | Authentication is done via the Kashier-Hash header. The hash is calculated using HMAC-SHA256 with your API key and a specific path format. ## Hash calculation [#hash-calculation] ```js const path = `/?payment=${merchantId}.${order.reference}.${order.amount}.${order.currency}`; const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, apikey).update(path); const hash = hmac.finalize().toString(CryptoJS.enc.hex); ``` This is the standard payment hash — see [Request hashing](/docs/direct-api/hashing#hashing) for the full recipe and the other signed-string forms. ## Using the Postman collection [#using-the-postman-collection] ### Prerequisites [#prerequisites] * Postman application installed * Your Kashier merchant ID * Your Kashier API key ### Setup [#setup] 1. Import the provided Postman collection JSON file 2. Update the collection variables: * merchantId: your merchant ID * apikey: your API key * The collection already includes pre-defined values for test-fep and live-fep ### Execution [#execution] 1. Select the "Generate Basata Code" request 2. The pre-request script will automatically: * Generate a unique order reference * Create the request body * Calculate the authentication hash 3. Send the request to generate a Basata code ### Variables [#variables] | Variable | Description | | -------------- | ----------------------------------------------- | | test-fep | Test environment base URL | | live-fep | Production environment base URL | | merchantId | Your merchant ID | | apikey | Your API key | | hash | Calculated authentication hash (auto-generated) | | paymentRequest | JSON request body (auto-generated) | ## Error handling [#error-handling] The API may return error responses with appropriate HTTP status codes and error messages. Common error scenarios include: * Invalid authentication (incorrect hash) * Missing required parameters * Invalid parameter values * System errors Error responses will include a status field with a value other than "SUCCESS" and detailed error messages. ## Aman: installment at kiosk [#aman-installment-at-kiosk] Aman is an installment-at-kiosk method: instead of a static reference code, the customer completes an OTP-verified installment purchase through Aman. Use `paymentMethod.type: "aman"` for the order's payment method, the same way you'd use `basata` or `cash`. An Aman payment progresses through five steps, each a separate `apiOperation` against the order: | Step | `apiOperation` | What happens | | ----------------- | ------------------- | ----------------------------------------------------------- | | Initiate purchase | `initiate_purchase` | Starts the Aman flow for the order. | | Send OTP | `send_otp` | Aman sends a one-time password to the customer. | | Pay | `pay` | The customer's OTP is submitted and the purchase completes. | | Reconcile | `reconcile` | Kashier confirms the transaction status with Aman. | | Refund | `refund` | Reverses a completed Aman transaction. | Aman's purchase result is keyed on Aman's own numeric result code: `ResultID === 0` means success, and any other value is a failure. The `transactionResponseCode` returned to you mirrors that `ResultID` (or a fallback default code) rather than the standard `00`/`k_*` dictionary used by card and Basata payments. After a successful pay or reconcile, the order's payment-method data is enriched with the installment details Aman returns: | Field | Meaning | | ---------------------- | ---------------------------------- | | `transactionId` | Aman's transaction reference | | `cardNumber` | Card used for the installment | | `firstInstallmentDate` | Date of the first installment | | `lastInstallmentDate` | Date of the last installment | | `adminFees` | Administrative fee charged by Aman | As with Basata, treat the completion webhook (`event: pay`, `status: SUCCESS`) as the authoritative "customer has paid" signal, not any intermediate response. ## Cash: mark-as-paid (synthetic) [#cash-mark-as-paid-synthetic] `paymentMethod.type: "cash"` — generic Cash — is not a reference-code rail like Basata, and it makes no external call to any provider. It's an internal bookkeeping operation: submitting a `pay` request with `paymentMethod.type: "cash"` debits your merchant balance and immediately writes a transaction with a hard-coded `transactionResponseCode: "00"` / `"Approved"` response. Cash's SUCCESS response is synthetic — Kashier does not contact any external processor to authorize it. Unlike Basata, Aman, or a card payment, there is no real settlement-risk signal here: a Cash payment request always returns success. Use it only to record a payment you already collected through some other channel — not as a way to accept a real-time cash payment. ## Contact and other BNPL providers [#contact-and-other-bnpl-providers] Contact (`paymentMethod.type: "contact"`) is not a cash/kiosk method despite the superficial similarity — in Kashier it's a BNPL/installment provider with its own multi-step flow (categories, balance validation, tenure selection, OTP, pay, refund). See [Installment plans → BNPL providers](/docs/direct-api/installment-plans#bnpl-installment-providers) for Contact alongside Octo, Souhoola, Mogo, Tru, and Forsa. # Pay with token (/docs/direct-api/pay-with-token) To create new payment requests using the card token. ## Step 1: Generate hash [#step-1-generate-hash] The order hash is generated as explained in [Request hashing](/docs/direct-api/hashing#hashing). Append `.{customerReference}` to the signed string — it is required whenever `card.save`, `card.cardToken`, or `card.agreement` is present, and omitting it returns `403 INVALID_HASH_CHECK`. ## Step 2: Pay [#step-2-pay] | Endpoint | Value | | -------- | ---------------------------------------- | | URL | `https://test-fep.kashier.io/v3/orders/` | | Method | POST | Whenever you are ready for production, use the following production API endpoint URL instead. | Endpoint | Value | | -------- | ----------------------------------- | | URL | `https://fep.kashier.io/v3/orders/` | | Method | POST | ```bash curl -X 'POST' 'https://test-fep.kashier.io/v3/orders/' \ -H 'accept: application/json' \ -H 'Content-Type: application/json' \ -H 'Kashier-Hash: your_generated_hash' \ -d '{ "apiOperation": "PAY", "paymentMethod": { "type": "CARD", "card": { "cardToken": "", "securityCode": "" } }, "installments": { "planId": "" }, "connectedAccount": { "merchantId": "" }, "order": { "reference": "", "amount": "1", "currency": "EGP", "description": "" }, "customer": { "reference": "" }, "interactionSource": "ECOMMERCE", "reconciliation": { "webhookUrl": "", "merchantRedirect": "", "redirect": true }, "metaData": {}, "merchantId": "MID-12-34" }' ``` ## Body structure [#body-structure] ```json { "apiOperation": "PAY", "paymentMethod": { "type": "CARD", "card": { "cardToken": "123456789123456789", "securityCode": "", "enable3DS": true } }, "installments": { "planId": "" }, "origin": { "id": "" }, "connectedAccount": { "merchantId": "" }, "order": { "reference": "", "amount": "1", "currency": "EGP", "description": "" }, "customer": { "reference": "" }, "interactionSource": "ECOMMERCE", "reconciliation": { "webhookUrl": "", "merchantRedirect": "", "redirect": true }, "metaData": {}, "merchantId": "" } ``` ## Body parameters [#body-parameters] | Parameter | Required | Description | | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `apiOperation` | Yes | `PAY` — a confirmation purchase against the saved token. | | `paymentMethod.type` | Yes | `CARD`. | | `paymentMethod.card.cardToken` | Yes | The saved card token, used instead of `paymentMethod.card.number`. | | `paymentMethod.card.securityCode` | Conditional | The CVV. Mandatory when `interactionSource` is `ECOMMERCE`. | | `paymentMethod.card.enable3DS` | No | Whether to run 3D Secure. Note it belongs inside `card`, not directly under `paymentMethod`. | | `installments.planId` | No | Send only when paying with an [installment plan](/docs/direct-api/installment-plans). Omit `installments` entirely for a regular charge. | | `origin.id` | No | Origin identifier. Required only when you send an `origin` object. | | `connectedAccount.merchantId` | No | The Sub Merchant / [connected account](/docs/accept-payments/connected-accounts) MID (`MID-…`) to charge on behalf of. | | `order.reference` | Yes | Your merchant order ID. | | `order.amount` | Yes | Order amount. | | `order.currency` | Yes | `EGP`, `USD`, `GBP`, or `EUR`. | | `order.description` | No | Order description. | | `customer.reference` | Yes | Shopper reference. Mandatory for any token payment — and it must also be appended to the signed hash string. | | `interactionSource` | Yes | Must be `ECOMMERCE`, `MOTO`, or `RECURRING` for direct card pay. Use `RECURRING` for merchant-initiated repeat charges. | | `reconciliation.webhookUrl` | No | Per-request server webhook URL. | | `reconciliation.merchantRedirect` | No | Where to send the customer afterwards (their receipt page). | | `reconciliation.redirect` | No | Redirect to `merchantRedirect` once the transaction completes. | | `metaData` | No | Arbitrary data echoed back in responses and webhooks. | | `merchantId` | Yes | Your merchant ID (`MID-…`). | Some published samples of this request also carry a top-level `timestamp` field. It is not in any documented field reference, so whether Kashier requires it is unconfirmed — we have left it out of the samples above rather than assert it. If you already send it, keeping it does no harm. ## Headers [#headers] | Key | Description | | --------------------- | ---------------------------------------------------------------------------- | | Kashier-Hash (String) | Order hash [generated in Request hashing](/docs/direct-api/hashing#hashing). | Full parameter and response reference → [Pay with token](/docs/api-reference/tokens/payWithToken). ## Responses [#responses] With `interactionSource` MOTO (non-3DS), the payment is processed and captured directly. With Ecommerce and 3DS, after a successful response you should redirect to `authentication.redirectUrl` to generate the 3DS page. As soon as the transaction has completed, it will redirect to `merchantRedirectUrl` if `reconciliation.redirect` was equal to true; otherwise [use 3D Secure handling](/docs/direct-api/3d-secure). You can receive the transaction response after 3DS processing in the [webhook](/docs/webhooks/payloads). # Retrieve tokens (/docs/direct-api/retrieve-tokens) A GET request is made to the "get customer card token and info" endpoint. ## Hashing [#hashing] Token requests are validated with a `Kashier-Hash` header: an HMAC SHA256 of the tokenization path, keyed with your **Payment API Key**. Generate it in your backend. The Secret Key goes in the `Authorization` header; the **Payment API Key** keys the hash. Swapping them is the usual cause of `400 invalid authorization` on this endpoint. Both are mode-scoped — a test key only validates on the `test-` hosts. ```js //Copy and paste this code in your Backend let crypto = require('crypto'); function generateKashierTokenHash() { const mid = 'MID-123-123'; //your merchant id const CustomerReference = '1'; //your customer id the card was saved against const apiKey = 'yourPaymentApiKey'; const path = `/?tokenization=${mid}.${CustomerReference}`; return crypto.createHmac('sha256', apiKey).update(path).digest('hex'); } ``` ```php //Copy and paste this code in your Backend function generateKashierTokenHash(){ $mid = "MID-123-123"; //your merchant id $apiKey = "yourPaymentApiKey"; $CustomerReference = "100"; //your customer id the card was saved against $path = "/?tokenization=".$mid.".".$CustomerReference; return hash_hmac('sha256', $path, $apiKey, false); } ``` ```python #Copy and paste this code in your Backend import hmac import hashlib def generate_kashier_token_hash(): mid = "MID-123-123" # your merchant id CustomerReference = "100" # your customer id the card was saved against path = '/?tokenization={}.{}'.format(mid, CustomerReference).encode('utf-8') api_key = "yourPaymentApiKey".encode('utf-8') return hmac.new(api_key, path, hashlib.sha256).hexdigest() ``` ```csharp //Copy and paste this code in your Backend using System.Security.Cryptography; public class Kashier { public static string create_hash(){ string mid = "MID-123-123"; //your merchant id string CustomerReference = "1"; //your customer Id string secret = "yourPaymentApiKey"; string path = "/?tokenization=" + mid + "." + CustomerReference; string message; string key; key = secret; message = path; System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); byte[] keyByte = encoding.GetBytes(key); byte[] messageBytes = encoding.GetBytes(message); HMACSHA256 hmacmd256 = new HMACSHA256(keyByte); byte[] hashmessage = hmacmd256.ComputeHash(messageBytes); return ByteToString(hashmessage).ToLower(); } public static string ByteToString(byte[] buff){ string sbinary = ""; for (int i = 0; i < buff.Length; i++){ sbinary += buff[i].ToString("X2"); // hex format } return (sbinary); } } ``` For creating the hash, use only the parameters mentioned in the snippet above. Don't add extra parameters in the hash creation. ## Retrieve tokens [#retrieve-tokens] In case you are still in the development phase, you will need to call our API using the following testing endpoint API URL. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------- | | TEST-URL | `https://test-fep.kashier.io/v3/cards/customer?customerReference=yourcustomerReference&merchantId=merchantId` | | LIVE-URL | `https://fep.kashier.io/v3/cards/customer?customerReference=yourcustomerReference&merchantId=merchantId` | | Method | GET | ## Headers [#headers] | Key | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------------- | | Authorization | Your Secret Key. | | Kashier-Hash (String) | HMAC SHA256 of `/?tokenization={mid}.{customerReference}`, keyed with your Payment API Key — see [Hashing](#hashing) above. | ```bash curl -X 'GET' 'https://test-fep.kashier.io/v3/cards/customer?customerReference=yourcustomerReference&merchantId=merchantId' \ -H 'Authorization: your_secretKey' \ -H 'Kashier-Hash: your_generated_hash' \ -H 'accept: application/json' ``` A customer with no saved card returns `400` with `There are no cards with this token` — that is an empty result, not an authentication failure. Full parameter and response reference → [Retrieve tokens](/docs/api-reference/tokens/retrieveTokens). # Wallet payments (/docs/direct-api/wallet-payments) Charge a customer's mobile wallet in two calls: initiate the wallet payment, then reconcile its status. ## Initiate wallet payment [#initiate-wallet-payment] | Endpoint | Value | | -------- | ---------------------------------------- | | TEST | `https://test-fep.kashier.io/v3/orders/` | | Method | POST | | LIVE | `https://fep.kashier.io/v3/orders/` | | Method | POST | ### Headers [#headers] | Key | Value | | ------------ | ---------------------------------------------------------------------------------- | | Kashier-Hash | Hash generated as explained in [Request hashing](/docs/direct-api/hashing#hashing) | | Content-Type | application/json | ### Request body [#request-body] ```json { "apiOperation": "INITIATE_R2P", "paymentMethod": { "type": "wallet" }, "order": { "reference": "PM-{timestamp}", "amount": 100, "currency": "EGP" }, "customer": { "mobilePhone": "01001001001" }, "interactionSource": "ECOMMERCE", "reconciliation": { "webhookUrl": "http://your-webhook-url" }, "merchantId": "{your_merchant_id}" } ``` ### Body description [#body-description] | Key | Type | Description | Required | | -------------------- | ------ | ---------------------------------------------------------- | -------- | | apiOperation | String | Must be "INITIATE\_R2P" | true | | order.reference | String | Unique order reference (prefixed with 'PM-' and timestamp) | true | | order.amount | Number | Payment amount | true | | order.currency | String | Currency code (e.g., "EGP") | true | | customer.mobilePhone | String | Customer's wallet mobile number | true | | webhookUrl | String | URL to receive payment notifications | true | | merchantId | String | Your merchant identifier | true | ### Success response [#success-response] ```json { "response": { "apiOperation": "INITIATE_R2P", "operation": "initiate_r2p", "result": "SUCCESS", "status": "SUCCESS", "order": { "systemOrderId": "19b76ede-8236-4dec-a2d5-80a373508188", "reference": "PM-1711283427747" }, "transactionId": "TX-21232888547", "transactionResponseMessage": { "en": "Request to pay message send successfully.", "ar": "" } }, "status": "SUCCESS" } ``` ### Response description [#response-description] | Key | Type | Description | | ------------- | ------ | -------------------------------------------------------- | | systemOrderId | String | Unique identifier for the order, used for reconciliation | | reference | String | Your original order reference | | transactionId | String | Unique transaction identifier | | status | String | Transaction status ("SUCCESS", "FAILED", etc.) | If the response code is `k_5` — "the mobile number is not registered on any provider" — the number cannot be charged. Treat `k_5` as terminal, not transient. Kashier also skips scheduling its automatic reconciliation for that order, so it never self-resolves: do not keep polling it, and ask the customer for a wallet number registered with a provider. ## Payment reconciliation [#payment-reconciliation] | Endpoint | Value | | -------- | -------------------------------------------------------------------------- | | TEST | `https://test-fep.kashier.io/v3/orders/:merchantOrderId or :systemOrderId` | | LIVE | `https://fep.kashier.io/v3/orders/:merchantOrderId or :systemOrderId` | | Method | PUT | ### Headers [#headers-1] | Key | Value | | ------------ | ---------------- | | Accept | application/json | | Content-Type | application/json | The initiate call above is gated by `Kashier-Hash`, but we have not been able to confirm from Kashier's own references what the `PUT` reconcile leg expects — the published header list for it has only `Accept` and `Content-Type`, and we would rather say so than invent a header. If your reconcile is rejected as unauthorized, send the same `Kashier-Hash` you generated for the initiate call and contact Kashier support to confirm the expected auth for this leg. ### Request body [#request-body-1] ```json { "apiOperation": "RECONCILE_WALLET", "paymentMethod": { "type": "wallet" }, "merchantId": "{your_merchant_id}" } ``` ### Body description [#body-description-1] | Key | Type | Description | | ------------------ | ------ | --------------------------- | | apiOperation | String | Must be "RECONCILE\_WALLET" | | paymentMethod.type | String | Must be "wallet" | | merchantId | String | Your merchant identifier | The order to reconcile is identified by the **path**, not the body: put the `systemOrderId` returned by the initiate request (or your own `merchantOrderId`) in place of `:merchantOrderId or :systemOrderId` in the URL above. ### Success response [#success-response-1] ```json { "response": { "apiOperation": "RECONCILE_WALLET", "result": "SUCCESS", "status": "SUCCESS", "paymentMethod": { "wallet": { "paidThrough": "Tahweel", "payScheme": "TestSchema", "payerName": "Test Account", "payerAccount": "01001001001" } }, "amount": 100, "totalCapturedAmount": 100, "totalAuthorizedAmount": 100, "transactionResponseMessage": { "en": "Approved", "ar": " " } }, "status": "SUCCESS" } ``` ### Response description [#response-description-1] | Key | Type | Description | | --------------------- | ------ | ----------------------- | | status | String | Payment status | | paidThrough | String | Wallet provider name | | payerName | String | Customer name | | payerAccount | String | Customer wallet number | | totalCapturedAmount | Number | Total amount captured | | totalAuthorizedAmount | Number | Total amount authorized | ### Reconcile outcomes [#reconcile-outcomes] A reconcile does not always come back `SUCCESS`. Branch on the returned `status`. | Status | HTTP | What to do | | --------- | ---- | ---------------------------------------------------------------------------------------------------------- | | `SUCCESS` | 200 | The customer approved. The order moves to `CAPTURED`, settlement runs and the webhook fires. Stop polling. | | `PENDING` | 200 | The customer has not acted on the request yet. This is normal, not an error — keep polling. | | `FAILURE` | 400 | Declined or timed out. Stop polling and start a new payment if the customer wants to retry. | If a pay transaction already exists for the order, reconcile returns the stored result instead of calling the provider again. ## Error handling [#error-handling] | Code | Description | | ---- | --------------------- | | 200 | Success | | 400 | Bad Request | | 401 | Unauthorized | | 403 | Forbidden | | 404 | Not Found | | 500 | Internal Server Error | ## Webhook notification [#webhook-notification] When a payment is completed, a webhook notification is sent to the specified webhookUrl with the payment status and details. Ensure your webhook endpoint is configured to handle these notifications securely. ## Best practices [#best-practices] 1. Always store the systemOrderId returned from the initiate request 2. Implement proper error handling and retries 3. Validate webhook notifications using signature verification 4. Use appropriate timeouts for reconciliation requests 5. Implement idempotency for payment requests ## Security considerations [#security-considerations] 1. Never expose your API key in client-side code 2. Always validate webhook signatures 3. Use HTTPS for all API calls 4. Implement request timeout handling 5. Store sensitive payment data securely # API keys (/docs/get-started/api-keys) Kashier authenticates your API requests using your account's API keys. If you do not include your key when making an API request, or if you use one that is incorrect or outdated, Kashier returns an error. Every account is provided with separate keys for testing and running live transactions. All API requests exist in either test or live mode, so one mode cannot interact with objects in the other. There are also two types of API keys: Payment API Keys and Secret API Keys. * Payment API Keys are used to sign requests and verify Kashier's responses. You use this key to compute the **order hash** — an HMAC-SHA256 signature over the order's details that proves the amount and order ID were not tampered with in the customer's browser — and to verify the signature Kashier sends back. See [request hashing](/docs/direct-api/hashing). * Secret API Keys should be kept confidential and stored only on your own servers. A Secret API Key authenticates the request; it does not by itself authorize every operation. What it can do is bounded by the **role and permissions attached to the user the key belongs to** — routes are individually checked against privileges such as `balance.all.view_balance` for settlement and balance reads, and a key whose role lacks the privilege is rejected even though the key itself is valid. On top of that, calls are subject to an optional IP allow-list you configure per merchant; requests from a non-allow-listed IP are rejected with a 403 error. Some capabilities are also gated per merchant and off until Kashier enables them — see [Capabilities are enabled per merchant](/docs/dashboard-api#capabilities-are-enabled-per-merchant). Each account has a total of four keys: a Payment API Key and a Secret Key pair for [test mode and live mode](#test-and-live-modes). ## Credentials at a glance [#credentials-at-a-glance] The docs and API refer to your credentials by different names depending on context — the prose term, the HTTP header you send it in, and the identifier Kashier's tooling uses internally aren't always the same word. Use this table to map between them: | Credential | HTTP header / body field | Other identifiers | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Payment API Key | `api-key` header | `apiKey` — OpenAPI security-scheme ID (the [API reference](/docs/api-reference) playground stores it in your browser's localStorage under `fumadocs-openapi-auth-apiKey`) | | Secret Key | `Authorization` header (raw value, not a Bearer token) | `secretKey` — OpenAPI security-scheme ID (stored as `fumadocs-openapi-auth-secretKey` in the playground) | | Merchant ID (MID) | `merchantId` — JSON request-body field (most endpoints); `mid` — query parameter on some endpoints (e.g. [installment plans](/docs/direct-api/installment-plans)) | `authmerchantid` — HTTP header, used only for [multi-merchant accounts](#multi-merchant-accounts-the-authmerchantid-header) | ## Obtaining your keys and MID [#obtaining-your-keys-and-mid] Your API keys are always available in the [Dashboard](https://merchant.kashier.io/en/dashboard/integration). The MID can be found under your username in the dashboard's top navigation bar. API keys Secret keys Use only your test API keys for testing and development. This ensures that you don't accidentally modify your live customers or charges. ## Multi-merchant accounts: the `authmerchantid` header [#multi-merchant-accounts-the-authmerchantid-header] If your user has access to more than one merchant account, include an `authmerchantid` header alongside your `Authorization` header on every Dashboard/Integration API request. Kashier resolves the active MID from this header (or from `selectedMID` carried in your session token); without it, a multi-merchant user has no way to tell the API which merchant a request applies to. ```http GET /v2/customers HTTP/1.1 Host: test-api.kashier.io Authorization: f86a28e62b452ee94a32dc49cde00047$491a5ae27e91294e97247b742d1727600d7b17297309820c590e0cbc0d8b48bc923aa94c5501619882248e58eb7cc authmerchantid: MID-00-000 ``` If your user only has access to a single merchant, you can omit this header. ## Managing keys and access via the API [#managing-keys-and-access-via-the-api] In addition to the Dashboard, you can manage your API keys, secret keys, and IP allow-list programmatically. All of these endpoints live on the same host as your other Dashboard/Integration API calls (`(test-)api.kashier.io`) and are authenticated the same way — with your Secret Key in the `Authorization` header (plus `authmerchantid` if your user has access to multiple merchants). ### API keys and secret keys [#api-keys-and-secret-keys] | Action | Method | Path | | ------------------------------------- | ------ | ------------------------------------ | | List your Payment API Keys | GET | `/v2/merchants/api-keys` | | Create a Payment API Key | PUT | `/v2/merchants/api-keys` | | Delete a Payment API Key | DELETE | `/v2/merchants/api-keys/:apiKeyId` | | List your Secret Keys | GET | `/v2/identity/secret-keys` | | Update a user's Secret Keys | PUT | `/v2/identity/secret-keys/:userId` | | Validate a set of Secret/Payment keys | POST | `/v2/merchants/validate-credentials` | ### IP allow-list [#ip-allow-list] Manage the IP allow-list that restricts which IPs may use your Secret Key: | Action | Method | Path | | --------------------------------------- | ------ | --------------------------- | | List allow-listed IPs | GET | `/v2/ip-address` | | Add an IP to the allow-list | POST | `/v2/ip-address` | | Get a single allow-list entry | GET | `/v2/ip-address/:ipId` | | Update an allow-list entry | PUT | `/v2/ip-address/:ipId` | | Remove an IP from the allow-list | DELETE | `/v2/ip-address/:ipId` | | Enable or disable the entire allow-list | PUT | `/v2/ip-address/toggle-all` | ## Test and live modes [#test-and-live-modes] The test and live modes function almost identically for integration testing and education, with a few necessary differences: * In test mode, payments are not processed by card networks or payment providers, and only our [test payment information](/docs/get-started/testing) can be used. * Payment and Secret Keys differ for each mode. * Base URLs are prefixed with 'test-'. * You may use and test features before being [onboarded and activated](https://merchant.kashier.io/en/onboarding). The status of the created account will be marked as a "Test" account by default. This will allow you to test all of Kashier's services. When you finish your testing phase, you should switch to live mode and obtain your live keys if you are [onboarded and activated](https://merchant.kashier.io/en/onboarding). Switch between test and live modes using the "It's live data" toggle from the sidebar, as shown below. Live data toggle Always ensure you are in the correct mode by checking the mode toggle at the bottom of the side navigation bar. Verify that you are using the correct keys for your intended mode. Kashier only matches a Payment API Key whose mode equals the host's mode. A test key validates only on the `test-` hosts and a live key only on the live hosts, so a mode mismatch fails [request hashing](/docs/direct-api/hashing) even when the formula is correct. This is the most common cause of an "invalid hash" error. # Going live (/docs/get-started/going-live) Swapping your keys and dropping the `test-` prefix is the smallest part of going live. Test mode is deliberately permissive: it seeds payment methods you may not own, forces the checkout to a fixed method list, skips settlement entirely, and applies no rate limit. An integration that passes every sandbox test can still fail on its first live request. Work through the checklist below before you point real customers at Kashier. Test mode does not read your real payment configuration. It seeds a default method set for your account and, on hosted checkout and the embedded iframe, overrides whatever you asked for with card and wallet only. Neither behaviour reflects what your live account can actually charge. Verify every method against your live account — see [Payment methods](#2-payment-methods). ## 1. Account, credentials, and hosts [#1-account-credentials-and-hosts] * [ ] **Activate the account for live mode.** Every account starts as a test account. Until Kashier has [onboarded and activated](https://merchant.kashier.io/en/onboarding) yours, live mode will not accept writes. *Skip it and* every write request to a live host is rejected with `Merchant is not live.` — the message names the account status, not your credentials, so it is easy to misread as an auth failure. * [ ] **Swap in your live keys.** You have four keys in total: a Payment API Key and a Secret Key for test, and the same pair for live. Keys are mode-scoped and cannot cross over. See [API keys](/docs/get-started/api-keys). *Skip it and* a test key against a live host fails authentication outright, and a test Payment API Key produces an "invalid hash" error even when your hashing formula is correct. * [ ] **Swap every base URL**, using the table below. Grep your codebase for `test-` afterwards — a single missed host silently keeps part of your flow in the sandbox. | What you call | Test host | Live host | | --------------------------------------------------------------------------------------------------- | --------------------- | --------------------- | | Dashboard / management API (sessions, transactions, customers, payment links, settlement, webhooks) | `test-api.kashier.io` | `api.kashier.io` | | Payment API / FEP (pay, capture, void, refund, tokens, transfers) | `test-fep.kashier.io` | `fep.kashier.io` | | Hosted checkout and the embedded checkout script | `payments.kashier.io` | `payments.kashier.io` | The checkout host is the same in both modes — the mode travels on the `sessionUrl` as a `mode` query parameter rather than in the hostname. If you load checkout assets from any other host, confirm its live equivalent with Kashier before you switch. * [ ] **Update your IP allow-list before your egress IP changes.** The allow-list is opt-in — an empty list allows every IP — but once it has at least one entry, only those IPs may use your Secret Key. Manage it from [API keys](/docs/get-started/api-keys#ip-allow-list). *Skip it and* every Secret Key call from the new server is rejected with `403 Unauthorized IP address`, which looks nothing like an IP problem unless you read the message. This is one of the more common causes of a go-live-day outage. Dashboard session callers are not IP-checked, so the dashboard will keep working while your server does not. * [ ] **Check the role behind the live Secret Key.** A Secret Key authenticates the request; what it may *do* is bounded by the role assigned to the user it belongs to (role-based access control, or RBAC — each route checks the caller's role for a named privilege before it runs). Settlement reads, for example, require `balance.all.view_balance` and settlement exports require `balance.all.export_balance`. *Skip it and* a perfectly valid key is rejected on individual routes. See [API keys](/docs/get-started/api-keys) and [error responses](/docs/dashboard-api/error-responses). * [ ] **Point signature verification at the live Payment API Key.** The redirect signature after 3-D Secure and the webhook signature are both computed with the Payment API Key *for that mode*. *Skip it and* every live redirect and webhook fails verification and you reject genuine payments. See [hashing and signatures](/docs/direct-api/hashing) and [signature verification](/docs/webhooks#step-4-verify-the-signature). ## 2. Payment methods [#2-payment-methods] This is the section that catches most integrations, because test mode is misleading in two different directions at once. * [ ] **Confirm every method you offer is actually enabled on your live account.** Which methods a merchant has is derived from that merchant's active payment configuration *in the current mode*. In live mode the list starts empty and is filled only from real configuration; in test mode Kashier seeds a default set — card, wallet, installments, InstaPay, and Basata — regardless of what you own. *Skip it and* a method your sandbox happily displayed is simply absent at live checkout. * [ ] **Test-only methods you could never have exercised.** Outside live mode, hosted checkout and the embedded iframe ignore `allowedMethods` entirely and force the list to card and wallet. `bank_installments`, `fawry`, and the rest cannot be exercised in the sandbox at all — so your first real bank-installment or kiosk payment is also your first test of that code path. Plan one live transaction per method rather than trusting the sandbox: use the smallest amount the method accepts, run it on your own card or wallet, and refund it straight away so the verification costs you only the transaction fee. Do this before you announce the method to customers, not after. See [payment sessions](/docs/accept-payments/payment-sessions). * [ ] **Handle declines you never saw.** Test outcomes are driven by the CVV and expiry date you submit, so you only ever saw the handful of results listed in [test cards](/docs/get-started/testing). Live traffic reaches real acquirers and returns the full code set — including non-deterministic outcomes that are neither paid nor failed. Make sure your code reads the response code rather than branching on success/failure alone. See [payment reason codes](/docs/accept-payments/payment-reason-codes) and [order reconciliation](/docs/accept-payments/order-reconciliation). ## 3. Capabilities enabled for your account [#3-capabilities-enabled-for-your-account] * [ ] **Confirm every capability you depend on is switched on.** Kashier gates many capabilities behind per-merchant flags, and a brand-new merchant has exactly three enabled: multiple balance accounts, payment links, and customers. Everything else — bulk charge, instant settlement, payment fees, branches, POS auto-settlement, connected accounts, and more — is off until Kashier enables it for you, and you cannot toggle a flag yourself. *Skip it and* the call fails even though your keys, path, and payload are all correct. There is no single status code for it: some gates reject a disabled capability with `400`, others with `401 Unauthorized`, so don't build a check around one of them. See [capabilities are enabled per merchant](/docs/dashboard-api#capabilities-are-enabled-per-merchant). Flags are an internal Kashier change against your merchant record, so raise them with your Kashier contact before your go-live date rather than on it. Kashier has not published a turnaround time — ask when you request the change. ## 4. Webhooks [#4-webhooks] * [ ] **Create a live-mode webhook.** Webhooks are scoped to a mode: a webhook with `mode` set to `test` only ever receives test events. Create a second webhook for live rather than flipping your existing one back and forth, so your staging listener keeps working. See [manage webhooks](/docs/webhooks/manage). *Skip it and* live payments complete with no notification reaching your server at all. * [ ] **Point the live webhook at a public HTTPS URL.** `localhost`, loopback, link-local, and private addresses are rejected — a tunnel URL that worked during development may not be what you want in production. * [ ] **Verify live deliveries with the live Payment API Key.** Transaction events are signed with the Payment API Key for the webhook's mode. * [ ] **Check your subscribed event list.** Matching is exact, so a webhook subscribed to `refund` is not delivered a `partial_refund`. Review the subscription you create for live, rather than assuming it mirrors test. ## 5. Behaviour that only exists on live [#5-behaviour-that-only-exists-on-live] * [ ] **Settlement windows.** Settlement windows and batches are produced by the live settlement pipeline only — in test mode there are none, so any reconciliation code that reads them has never run against real data. Exercise it after your first live captures. See [settlement](/docs/accept-payments/settlement). * [ ] **Rate limits.** There is no rate limit on the test environment. On live, checkout requests are limited to 1000 requests per minute **per IP address** — so services sharing one outbound IP or sitting behind a NAT share a single bucket. Kashier has not published the reset behaviour or response shape, so handle failures gracefully rather than coding against a specific error format. See [rate limits](/docs/resources/rate-limits). * [ ] **Real money.** Live refunds, voids, and payouts move real funds. Make sure the operational paths — [refunds](/docs/accept-payments/refunds), [void](/docs/accept-payments/void) — are wired up and permissioned before you need them under pressure, not after. ## 6. After the first live payment [#6-after-the-first-live-payment] Run one small real transaction end to end before you open the flow to customers, and confirm each link in the chain rather than only the checkout result: * [ ] The order reaches the expected status — check it with [order reconciliation](/docs/accept-payments/order-reconciliation), not only the browser redirect. * [ ] The redirect signature verified against your live Payment API Key. * [ ] Your live webhook received the event and its signature verified. * [ ] The refund path works, by refunding that transaction. * [ ] The transaction appears in a settlement window once one closes. ## What does not change [#what-does-not-change] Request and response shapes, the hashing formula, error envelopes, and endpoint paths are identical in both modes. Test mode also stays available after you go live — the two modes are fully isolated and one can never touch the other's objects, so you can keep developing against test with live traffic running. # Introduction (/docs/get-started) Kashier is a payment gateway for Egypt. As a developer you can accept online payments, run your own checkout, receive webhooks, send payouts, drive POS terminals, and automate dashboard operations — all testable for free in test mode before anything goes live. The [quick start](/docs/get-started/quickstart) takes you from signup to a completed test payment in about five minutes, and the [API playground](/docs/api-reference) lets you run every endpoint from your browser. ## What you can build [#what-you-can-build] ### Kashier payments [#kashier-payments] Integrate secure payment processing into your web and mobile applications. Kashier offers [payment sessions](/docs/accept-payments/payment-sessions) with hosted or [embedded checkout](/docs/accept-payments/payment-sessions#step-2-send-the-customer-to-pay), plus [payment links](/docs/accept-payments/payment-links), [Apple Pay](/docs/accept-payments/apple-pay), and [recurring payments](/docs/accept-payments/recurring). For full control over your own card form, use the [direct API](/docs/direct-api). ### Kashier Dashboard API [#kashier-dashboard-api] Integrate the [Dashboard API](/docs/dashboard-api) into your server applications for an automated experience without manual dashboard operations — create and share invoices, sync inventory, process refunds, and more. ### Third-party plugins [#third-party-plugins] Plug and play with ready-made [plugins](/docs/plugins): [WooCommerce](/docs/plugins/woocommerce), [Shopify](/docs/plugins/shopify), [PrestaShop](/docs/plugins/prestashop), [Magento](/docs/plugins/magento), [OpenCart](/docs/plugins/opencart), [Booking BA](https://ba-booking.com/shop/downloads/babe-payment-kashier), [CS-Cart](/docs/plugins/cs-cart), and [VikBooking and VikAppointments](/docs/plugins/vik-booking). ## How these docs are organized [#how-these-docs-are-organized] | Section | What it covers | | ------------------------------------------ | ------------------------------------------------------------------------------- | | [Get started](/docs/get-started) | Test account, API keys, test cards, first payment, going live | | [API reference](/docs/api-reference) | Every endpoint with a runnable playground — **this is where you try the APIs** | | [Accept payments](/docs/accept-payments) | Guides for sessions, checkout, links, Apple Pay, recurring, refunds, settlement | | [Direct API integration](/docs/direct-api) | Your own card form, tokens, 3D Secure, wallets, installments | | [Webhooks](/docs/webhooks) | Server-to-server event notifications | | [Payouts](/docs/payouts) | Sending money out to banks and wallets | | [In-person payments](/docs/pos) | POS terminal integrations | | [E-commerce plugins](/docs/plugins) | Store-platform setup guides, no code | | [Dashboard API](/docs/dashboard-api) | Automating dashboard operations | | [Resources](/docs/resources) | [AI tools](/docs/resources/ai-tools), Postman, support | ## Test and live modes [#test-and-live-modes] You can use the Kashier APIs in test mode to test your integrations — test base URLs are prefixed with `test-` and no real money moves. See [API keys](/docs/get-started/api-keys). Swapping keys and hosts is only part of the switch, though: test mode seeds payment methods you may not own, forces the checkout to card and wallet, and produces no settlement. Work through [Going live](/docs/get-started/going-live) before you point real customers at Kashier. # Quick start (/docs/get-started/quickstart) ## 1. Get your test credentials [#1-get-your-test-credentials] Test vs live modes → [API keys](/docs/get-started/api-keys). ## 2. Create a payment session [#2-create-a-payment-session] Replace the three placeholders and run this — or open [Create payment session](/docs/api-reference/payment-sessions/createPaymentSession) and press **Send** with the same values in the playground: ```bash curl https://test-api.kashier.io/v3/payment/sessions \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'api-key: YOUR_TEST_API_KEY' \ --header 'Content-Type: application/json' \ --data '{ "expireAt": "2030-01-01T00:00:00.000Z", "maxFailureAttempts": 3, "paymentType": "credit", "amount": "100.00", "currency": "EGP", "order": "quickstart-001", "merchantId": "YOUR_MID", "merchantRedirect": "https://your-website.com/redirect", "display": "en", "type": "one-time", "allowedMethods": "card,wallet", "customer": { "email": "you@example.com", "reference": "customer-001" } }' ``` Ten of those fields are required; the session is rejected without them. The other two are worth sending anyway: | Field | | What it does | | -------------------- | -------- | --------------------------------------------------------------------------------------------- | | `expireAt` | required | When the session stops accepting payment. Must be a future ISO 8601 timestamp. | | `maxFailureAttempts` | required | How many failed attempts the customer gets before the session closes. | | `amount` | required | The amount to charge, as a string. | | `currency` | required | `EGP`, `USD`, `GBP`, or `EUR`. | | `order` | required | Your own order identifier. Must be unique. | | `merchantId` | required | Your `MID-…`. | | `merchantRedirect` | required | Where Kashier sends the customer after payment. | | `type` | required | `one-time` here. | | `display` | required | Checkout language, `en` or `ar`. | | `customer` | required | At minimum an `email` and your own `reference`. | | `paymentType` | optional | Defaults are applied when omitted. | | `allowedMethods` | optional | Restricts the methods offered at checkout. Omit to offer everything your account has enabled. | The response includes a `sessionUrl`: ```json { "status": "CREATED", "sessionUrl": "https://payments.kashier.io/session/…?mode=test" } ``` ## 3. Pay with a test card [#3-pay-with-a-test-card] Open the `sessionUrl` in your browser — this is the hosted checkout your customers see. Pay with: * Card number: `5123450000000008` (Mastercard, 3-D Secure enrolled) * Expiry: `06/25` · CVV: `100` The expiry is not arbitrary. In test mode it **selects the outcome** — `06/25` is the value that returns `APPROVED`, and other values return declines, timeouts, and errors on purpose. The full list is in [test cards](/docs/get-started/testing#expiry-date-response-codes). ## 4. Confirm the result [#4-confirm-the-result] Open your [dashboard](https://merchant.kashier.io) (test mode) — the payment is at the top of Transactions. Or fetch it over the API: [Get payment session](/docs/api-reference/payment-sessions/getPaymentSession). ## Next steps [#next-steps] # Test cards and testing (/docs/get-started/testing) You can use the following cards to test your integration with Kashier. ## Card numbers [#card-numbers] | Card type | Card number | Cardholder name | | ---------- | --------------------------------------------------------------------------------------------------------- | --------------- | | MasterCard | `5111111111111118` | Michel Doe | | MasterCard | `5123456789012346` | John Doe | | MasterCard | `5123450000000008` (3D-Secure enrolled) — use this one in the [quick start](/docs/get-started/quickstart) | John Doe | | Visa | `4012000033330026` | John Doe | | Visa | `4508750015741019` (3D-Secure enrolled) | John Doe | ## CSC/CVV response codes [#csccvv-response-codes] | CVV | Response GW code | | --- | ---------------- | | 100 | `MATCH` | | 101 | `NOT_PROCESSED` | | 102 | `NO_MATCH` | ## Expiry date response codes [#expiry-date-response-codes] In test mode the expiry date you submit **chooses the result**. These values are matched literally — they are not read as dates, and none of them has "expired". `06/25` still returns `APPROVED`, and `04/27` returns `EXPIRED_CARD` even though it is a future date. Send the value for the outcome you want to exercise; any expiry not in this table is not a defined trigger. | Expiry date | Transaction response code | | ----------- | ------------------------- | | 06/25 | `APPROVED` | | 05/25 | `DECLINED` | | 04/27 | `EXPIRED_CARD` | | 08/28 | `TIMED_OUT` | | 01/37 | `ACQUIRER_SYSTEM_ERROR` | | 02/37 | `UNSPECIFIED_FAILURE` | | 05/37 | `UNKNOWN` | ### Telling the failure codes apart [#telling-the-failure-codes-apart] `DECLINED`, `EXPIRED_CARD`, and `TIMED_OUT` describe the card or the attempt. The next two do not — they describe Kashier's side of the call, and neither is a judgement about the customer's card: | Code | What actually happened | What to do | | ----------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | `ACQUIRER_SYSTEM_ERROR` | The acquirer errored while processing the transaction. An infrastructure fault upstream of the card. | Treat the attempt as failed. Do **not** tell the customer their card was declined or ask for a different one — the same card may work on a retry. | | `UNSPECIFIED_FAILURE` | The gateway failed the transaction without reporting a reason. No further detail exists. | Same handling as above. Log the full response; it is the only thing Kashier support can work from. | Both are terminal for that attempt, which is what separates them from `UNKNOWN` below. Branch on the response code rather than on a success/failure boolean, so these two never reach the customer as "your card was declined". `UNKNOWN` means the provider gave no deterministic answer — a timeout, a busy gateway, or an order it cannot find. The transaction rolls up to order status `INITIATED`, not `FAILED`. Never treat it as paid or as declined, and never tell a customer to retry on the strength of it: a retry on a charge that did land double-charges them. Re-read the order with [order reconciliation](/docs/accept-payments/order-reconciliation) before you do anything else. ## Reading the reconciliation verdict [#reading-the-reconciliation-verdict] The API returns **`reconcilation`**, not `reconciliation`. The misspelling is in the API itself, so it is the spelling your code has to match. Reading `response.reconcilation` works; reading the correctly-spelled `response.reconciliation` returns `undefined` on every transaction and fails silently — you get no error, just a field that is never there. Order reconciliation returns a per-transaction `reconcilation` field. It is **not** a payment outcome. It says whether Kashier has reconciled that transaction against the provider, not whether money moved: | `reconcilation` | What it means | | --------------- | --------------------------------------------------------------------------- | | `NA` | The default — not yet reconciled. Carries no information about the payment. | | `OK` | Reconciled and consistent. A reconciled **failure** is also `OK`. | | `Failed` | Reconciliation ran but the two sides did not match. Investigate. | | `Not_Exists` | The counterpart record was not found on the side being reconciled. | `OK` does not mean the charge succeeded — it means the record is consistent, which is equally true of a reconciled `FAILURE`. Branch on the transaction's own `status` **together with** its `reconcilation`: * `status: SUCCESS` and `reconcilation: OK` — the charge landed. Do not retry. * `status: FAILURE` and `reconcilation: OK` — the charge is confirmed failed. Retrying is safe. * `reconcilation: NA` — nothing has been reconciled yet. Do not act on it at all; poll again, and escalate to Kashier support rather than re-charging the customer. * `Failed` or `Not_Exists` — treat the outcome as undetermined and escalate. Do not retry on the assumption that nothing landed. ## Mobile e-wallet [#mobile-e-wallet] | Wallet type | Mobile number | | ----------- | ------------- | | Vodafone | `01001001001` | Unlike cards, a wallet test payment has no trigger table — the outcome comes from whichever wallet provider is wired to your test account, and Kashier runs two different implementations behind the same request shape. If this number does not behave the way you expect, check the response code before assuming the number is wrong: * **`k_5`** — "the mobile number is not registered on any provider". This is terminal, and Kashier does not schedule its automatic reconciliation for that order, so it never resolves on its own. Do not keep polling it. It usually means your test account is wired to a provider this number is not registered with; ask Kashier support which wallet provider your account uses. * Anything else — see [wallet payments](/docs/direct-api/wallet-payments) for the full flow, including the `PENDING` reconcile result that means "not finished yet" rather than "failed". # Payouts (/docs/payouts) Send funds from your Kashier balance to bank accounts, cards, and mobile wallets through the Payouts API. Use it to pay vendors, suppliers, employees, or customers programmatically. ## Hosts [#hosts] Payouts span two hosts, and both are valid: | Host | Used for | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `api.kashier.io` (`test-api` in test) | Reads, lookups, and fee inquiry — account info, listing transfers, transfer details, batch tracking, schedulers, fee inquiry | | `fep.kashier.io` (`test-fep` in test) | Transfer writes — create transfer, bulk transfer | Each endpoint below states its own host. Use the host shown for that endpoint rather than assuming one base URL for the whole domain. Looking for account management or the balance ledger instead? See [Accounts and balance](/docs/account-and-balance) — that's also where [Get account info](/docs/account-and-balance/accounts#get-account-info) (`GET /v2/account`) now lives, alongside the rest of the account read/write endpoints. ## List all transfers [#list-all-transfers] To list all the transfers, you need to make a GET request to the list all transfers API. | Endpoint | Value | | -------- | --------------------------------------------------------------------- | | LIVE URL | `https://api.kashier.io/v2/transfers?limit=5&page=1&sortType=-1` | | Method | GET | | TEST URL | `https://test-api.kashier.io/v2/transfers?limit=5&page=1&sortType=-1` | | Method | GET | `limit`, `page`, and `sortType` are optional pagination controls — `page` is 1-based and `sortType` is `-1` for newest first or `1` for oldest first. ### Headers [#headers] | Key | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The authorization is a secret key used to identify the merchant. You can obtain this key from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl -X 'GET' 'https://test-api.kashier.io/v2/transfers?limit=5&page=1&sortType=-1' -H 'Authorization: your_secretKey' -H "accept: application/json" ``` Full parameter and response reference → [List all transfers](/docs/api-reference/payouts/listTransfers). ## Get transfer details [#get-transfer-details] To get transfer details, you need to make a GET request to the transfer details API, including the transfer ID as a parameter. | Endpoint | Value | | -------- | ------------------------------------------------------ | | LIVE URL | `https://api.kashier.io/v2/transfers/:transferId` | | Method | GET | | TEST URL | `https://test-api.kashier.io/v2/transfers/:transferId` | | Method | GET | ### Headers [#headers-1] | Key | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The authorization is a secret key used to identify the merchant. You can obtain this key from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl --location 'https://test-api.kashier.io/v2/transfers/{{transferId}}' \ --header 'Authorization: your_secretKey' \ --header 'Content-Type: application/json' \ ``` Full parameter and response reference → [Get transfer details](/docs/api-reference/payouts/getTransferDetails). ## Fees inquiry [#fees-inquiry] To inquire about the fees, you can make a POST request to the fees inquiry API. | Endpoint | Value | | -------- | ------------------------------------------------------ | | LIVE URL | `https://api.kashier.io/v2/transfers/fee-inquiry` | | Method | POST | | TEST URL | `https://test-api.kashier.io/v2/transfers/fee-inquiry` | | Method | POST | ### Headers [#headers-2] | Key | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The Authorization is a secret key used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl -X 'POST' 'https://test-api.kashier.io/v2/transfers/fee-inquiry' \ -H 'Authorization: your_secretKey' -H "accept: application/json" --data '{ "transfers": [ { "amount": "100", // transfer amount that you want to inquire about its fees "method": "wallet" // use card, wallet, bank or instant wallet for different methods } ] }' ``` Full parameter and response reference → [Fees inquiry](/docs/api-reference/payouts/feeInquiry). ## Transfer methods [#transfer-methods] `method` is lowercased before validation, so send it in lower case. | Method | Required fields | Dedicated batch route | | ------------------ | ------------------------------------------- | ------------------------- | | `bank` | `recipientBank` | — | | `wallet` | Egyptian mobile number in `recipientNumber` | — | | `instant wallet` | Egyptian mobile number in `recipientNumber` | `/batch/instant-wallet` | | `card` | `cardToken` and `recipientBank` | — | | `octo card` | Octo Payout recipient | `/batch/octo-payout` | | `internal account` | Internal account-to-account recipient | `/batch/internal-account` | The dedicated batch routes hang off the same transfers path as [Bulk transfers](#bulk-transfers) and take an XLSX upload. `/batch/instant-wallet` and `/batch/octo-payout` accept the columns `Recipient Name`, `Recipient Number`, `Amount`, `Merchant Transfer Id`, and force the method for every row. A generic `/batch` file whose rows mix methods is stored with the batch method `mixed`. ## Validation rules [#validation-rules] Requests that break these rules are rejected with a 400 before any money moves. | Rule | Limit | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Amount, minimum | `0.01` for every method | | Amount, wallet maximum | `60000` | | `recipientNumber` for `wallet` and `instant wallet` | Egyptian mobile number matching `^(010\|011\|012\|015)\d{8}$` | | `recipientNumber` for `bank` and `card` | Length only — 1–34 characters, the same bound that applies to every method. No format, checksum, or IBAN validation is applied, so a malformed account number is rejected by the receiving provider rather than by this API. | | `recipientName` | Trimmed, 3–70 characters | | Transfers per batch | 1–1000 | | `recipientBank` | Uppercased and matched against Kashier's supported-bank list — see [Bank codes](#bank-codes) for the downloadable list. Required for `bank` and `card`; ignored for every other method. | | `merchantTransferId` | Optional, but must be unique per merchant. Reusing one is rejected as a duplicate. | ## Bank codes [#bank-codes] `recipientBank` takes a bank **abbreviation**, not a name or a SWIFT code. It is uppercased before validation, and an abbreviation Kashier does not recognise is rejected with a 400 before any money moves. Download the full list here: [**Bank abbreviations (XLSX)**](/downloads/kashier-bank-abbreviations.xlsx) — every accepted `recipientBank` value with its bank name. ## Create transfer [#create-transfer] To create a transfer, you need to make a POST request to the create transfer API. | Endpoint | Value | | -------- | ------------------------------------------------- | | LIVE URL | `https://fep.kashier.io/v3/transfers/single` | | Method | POST | | TEST URL | `https://test-fep.kashier.io/v3/transfers/single` | | Method | POST | Kashier's own sources disagree on the canonical public entry point for creating a transfer: this page (and the sandbox examples) use `(test-)fep.kashier.io/v3/transfers/single`, while the transfers service documents the merchant entry point as `(test-)api.kashier.io/v2/transfers/single`. Both appear in Kashier's Postman collection. If the host above does not work for your account, try the `api.kashier.io/v2/transfers/single` form and confirm with your account manager which one you should build against. ### Headers [#headers-3] | Key | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The Authorization is a secret key used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | | kashier-hash | Request signature. **Only required when the `transfers hashing` capability is enabled on your account** — see below. | #### The `kashier-hash` request header [#the-kashier-hash-request-header] Transfer creation can be signature-protected. When Kashier enables the `transfers hashing` capability on your merchant account, every create-transfer request must carry a `kashier-hash` header; a missing or mismatched hash is rejected with a `403`. When the capability is off, the header is ignored. If you are getting a `403` on create with valid keys and no explanation, this is the first thing to check with your account manager. The value is the HMAC-SHA256 of the string below, hex-encoded, keyed by your **transfer API key** — a different key from the payment API key used for [order hashing](/docs/direct-api/hashing): ```text /?transfer={merchantId}.{method}.{recipientName}.{recipientNumber}.{amount} ``` If you send a `merchantTransferId`, append it as a sixth segment, and sign that variant instead: ```text /?transfer={merchantId}.{method}.{recipientName}.{recipientNumber}.{amount}.{merchantTransferId} ``` The key must match the mode you are calling in — a test transfer API key for the test hosts, a live one for live. The `kashier-hash` requirement is documented against the api-gateway create route (`/v2/transfers/single`). Whether it is also enforced on the `fep.kashier.io/v3/transfers/single` form shown above is **not confirmed** — check with your account manager if the capability is enabled on your account. ```bash curl -X 'POST' 'https://test-fep.kashier.io/v3/transfers/single' -H 'Authorization: your_secretKey' -H "accept: application/json" --data '{ "amount": 10, "method": "wallet", // use card, wallet, bank or instant wallet for different methods "recipientName": "Jhon Doe", "merchantTransferId":"TRF-YOUR-OWN-UNIQUE-ID", // optional; must be unique per merchant "recipientNumber": "01555539512" }' ``` It is optional, and reusing one is rejected as a duplicate — a fixed value in the docs would work for whoever sends first and fail for everyone after. Add your own when you send the request for real; it is the field you use to reconcile the transfer against your own records. The create call returns `status: "PENDING"` — the transfer is accepted, not sent. It then moves through `INITIATED` → `IN_TRANSIT` → `TRANSFERRED` or `FAILED` asynchronously, and the final provider result is never in this response. Track the outcome with [Get transfer details](#get-transfer-details) or the [payouts webhook](/docs/payouts/webhook). | Status | Meaning | | ----------- | ------------------------------------------------------------------------------------------------------------- | | PENDING | Transfer created, balance not yet debited. Returned by the create call. | | INITIATED | Balance debited; ready to send to the provider. | | IN\_TRANSIT | Sent to the provider, awaiting confirmation. | | TRANSFERRED | Completed. Final unless `openForReturn` is `true` — see [openForReturn](/docs/payouts/webhook#openforreturn). | | FAILED | Failed at any stage. Balance is reversed. | Full parameter and response reference → [Create transfer](/docs/api-reference/payouts/createTransfer). ## Bulk transfers [#bulk-transfers] To create a bulk transfer, you need to make a POST request to the create bulk transfer API. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------ | | LIVE URL | `https://fep.kashier.io/v3/transfers/batch?merchantBatchId=:merchantBatchId&batchName=:batchName` | | Method | POST | | TEST URL | `https://test-fep.kashier.io/v3/transfers/batch?merchantBatchId=:merchantBatchId&batchName=:batchName` | | Method | POST | ### Headers [#headers-4] | Key | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The Authorization is a secret key used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ```bash curl -X POST 'https://test-fep.kashier.io/v3/transfers/batch?merchantBatchId=:merchantBatchId&batchName=:batchName' \ -H 'Authorization: your_secretKey' \ -H "accept: application/json" \ -F 'file=@/path/to/file.xlsx' ``` The upload is an **XLSX** file sent as multipart form data under the field name `file`. Its columns are `method`, `recipientName`, `recipientNumber`, `recipientBank`, `amount`, `cardToken`, and `merchantTransferId`; the per-row `method` decides the rail, and a file whose rows mix methods is stored with the batch method `mixed`. Full parameter and response reference → [Bulk transfers](/docs/api-reference/payouts/createBulkTransfer). # Instant Settlement (/docs/payouts/instant-settlement) Instant Settlement lets you request an early payout of eligible, not-yet-settled transactions in exchange for a fee, ahead of their normal settlement window. Instant settlement is gated by the per-merchant `instant_settlement_request` feature flag, which is **off by default**. Only Kashier can enable it on your account — you can't toggle it yourself. Separately, each route requires an instant-settlement permission on the calling user's role — `instant_settlements.all.view_instant_settlement` for the reads on this page, `instant_settlements.all.create_instant_settlement` for the fee inquiry and for creating a request — so a valid key on a role without them is rejected even when the flag is on. Both refusals come from the same gate and look alike: a `401 Unauthorized`, not a `404`. Every path on this page **is** live on `test-api.kashier.io` — if you get a `401` on a valid key, the flag or the permission is missing, not the endpoint. (A request with no `Authorization` header at all is answered with `403 {"message": "No auth token provided"}`.) A typical flow: list your eligible transactions, optionally get amount suggestions or a fee quote, then create a request. Once created, a request moves through a small set of statuses: | Status | Meaning | | ------------- | ---------------------------------------------------------------------------------- | | `PENDING` | The request has been created and is waiting to be processed. | | `PROCESSING` | The early payout amount has been deducted from your balance. | | `TRANSFERRED` | The payout has reached your payout method. | | `DECLINED` | The request will not be processed. See `declineReason` on the request for details. | ## Get eligible transactions [#get-eligible-transactions] Returns a paginated list of your not-yet-settled transactions that are eligible for instant settlement, plus summary totals and your current caps. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v3/payment/instant-settlements/instant/eligible-transactions](https://test-api.kashier.io/v3/payment/instant-settlements/instant/eligible-transactions) | | LIVE-URL | [https://api.kashier.io/v3/payment/instant-settlements/instant/eligible-transactions](https://api.kashier.io/v3/payment/instant-settlements/instant/eligible-transactions) | | Method | GET | ### Query parameters [#query-parameters] | Key | Description | | -------- | ---------------------------------------------------------------------- | | page | Page number for pagination. Example: `1` | | limit | Records per page. Example: `20` | | channel | Optional filter: `online` or `pos`. | | method | Optional filter: `card` or `wallet`. | | dateFrom | Inclusive lower bound on transaction date (ISO). Example: `2026-06-01` | | dateTo | Inclusive upper bound on transaction date (ISO). Example: `2026-06-30` | ```bash curl --location 'https://test-api.kashier.io/v3/payment/instant-settlements/instant/eligible-transactions?page=1&limit=20' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response] ```json { "message": "success", "data": [ { "transactionId": "TX-1001", "amount": 10000, "settlementAmount": 10000, "accountId": "ACC-39550-436-01", "method": "card", "channel": "online", "transactionDate": "2026-06-10T11:20:00.000Z", "rfsDate": "2026-06-15T00:00:00.000Z" } ], "pagination": { "total": 2, "limit": 20, "page": 1, "pages": 1 }, "summary": { "count": 2, "totalAmount": 15000, "totalSettlementAmount": 15000 }, "limits": { "perRequestCap": 100000, "dailyCap": 250000, "usedToday": 0, "remainingToday": 250000 } } ``` | Field | Description | | ------------------------- | ---------------------------------------------------------------------------------------------- | | `data[].transactionId` | The transaction's identifier. | | `data[].settlementAmount` | The amount that would be settled for this transaction. | | `data[].accountId` | The account this transaction would settle into. | | `data[].rfsDate` | The transaction's regular ("ready for settlement") settlement date, absent instant settlement. | | `summary` | Totals across the whole eligible set, not just the current page. | | `limits.perRequestCap` | The maximum amount you can include in a single request. | | `limits.dailyCap` | The maximum amount you can request in a day. `0` means no daily cap is configured. | | `limits.usedToday` | Amount already requested today. | | `limits.remainingToday` | Remaining amount you can request today. `null` when `dailyCap` is unconfigured. | ## Get amount suggestions [#get-amount-suggestions] Given a target amount, returns the combination of your eligible transactions whose combined `settlementAmount` comes closest to that target — one combination just below it and one just above it — so you don't have to hand-pick transactions to hit a number. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v3/payment/instant-settlements/instant/suggestions](https://test-api.kashier.io/v3/payment/instant-settlements/instant/suggestions) | | LIVE-URL | [https://api.kashier.io/v3/payment/instant-settlements/instant/suggestions](https://api.kashier.io/v3/payment/instant-settlements/instant/suggestions) | | Method | POST | ### Body parameters [#body-parameters] | Key | Description | | ------------ | --------------------------------------------------------------- | | targetAmount | The amount you'd like to get as close to as possible. Required. | | channel | Optional filter: `online` or `pos`. | | method | Optional filter: `card` or `wallet`. | | dateFrom | Optional inclusive lower bound on transaction date (ISO). | | dateTo | Optional inclusive upper bound on transaction date (ISO). | ```bash curl --location 'https://test-api.kashier.io/v3/payment/instant-settlements/instant/suggestions' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --data '{ "targetAmount": 50000 }' ``` ### Headers [#headers-1] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-1] ```json { "targetAmount": 50000, "below": { "totalAmount": 38500, "transactionsCount": 4, "transactionIds": ["TX-A30000", "TX-B7000", "TX-C1000", "TX-D500"] }, "above": { "totalAmount": 55000, "transactionsCount": 2, "transactionIds": ["TX-A30000", "TX-E25000"] }, "candidatesConsidered": 5, "truncated": false, "approximate": false, "limits": { "perRequestCap": 100000, "dailyCap": 250000, "remainingToday": 250000 } } ``` | Field | Description | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `below` | The largest combination of transactions whose total is at or under `targetAmount`. `null` if no combination fits. | | `above` | The smallest combination of transactions whose total is at or over `targetAmount`. `null` if no combination fits. | | `below.transactionIds` / `above.transactionIds` | Pass these directly as `transactionIds` to the [fee inquiry](#get-a-fee-inquiry) or [create request](#create-an-instant-settlement-request) endpoints. | Both `below` and `above` are clamped to your effective cap (the lower of `perRequestCap` and your remaining daily cap). ## Get a fee inquiry [#get-a-fee-inquiry] Returns a fee breakdown for a set of transactions, without creating a request. Use this to show the merchant the net amount they'd receive before they commit. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v3/payment/instant-settlements/instant/inquiry](https://test-api.kashier.io/v3/payment/instant-settlements/instant/inquiry) | | LIVE-URL | [https://api.kashier.io/v3/payment/instant-settlements/instant/inquiry](https://api.kashier.io/v3/payment/instant-settlements/instant/inquiry) | | Method | POST | ### Body parameters [#body-parameters-1] | Key | Description | | -------------- | ------------------------------------------------------ | | transactionIds | Array of transaction IDs to quote a fee for. Required. | ```bash curl --location 'https://test-api.kashier.io/v3/payment/instant-settlements/instant/inquiry' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --data '{ "transactionIds": ["TX-1001", "TX-1002"] }' ``` ### Headers [#headers-2] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-2] ```json { "totalAmount": 15000, "totalSettlementAmount": 15000, "totalRateFees": 225, "vat": 31.5, "flatFees": 0, "totalFees": 256.5, "netTransferAmount": 14743.5, "transactionsCount": 2 } ``` | Field | Description | | ----------------------- | ----------------------------------------------------------------- | | `totalSettlementAmount` | Sum of the settlement amounts of the selected transactions. | | `totalRateFees` | Percentage-based instant settlement fee. | | `vat` | VAT on the fee. | | `flatFees` | Flat portion of the fee, if any. | | `totalFees` | `totalRateFees + vat + flatFees`. | | `netTransferAmount` | What you'd actually receive: `totalSettlementAmount - totalFees`. | If the selection would exceed your per-request or daily cap, this call fails before you ever create a request, with a `422` carrying a dedicated code — `INSTANT_SETTLEMENT_PER_REQUEST_LIMIT_EXCEEDED` or `INSTANT_SETTLEMENT_DAILY_LIMIT_EXCEEDED` — and the relevant figures: ```json { "status": "FAILURE", "error": { "code": "INSTANT_SETTLEMENT_DAILY_LIMIT_EXCEEDED", "message": "Instant settlement daily limit exceeded", "limit": 250000, "usedToday": 240000, "remaining": 10000 } } ``` Cap breaches are the one exception: every other validation failure on this module is a `400`, and a `409` means the request exists but is no longer `PENDING`. ## Create an instant settlement request [#create-an-instant-settlement-request] Creates a request over the selected transactions. The request starts in `PENDING` status. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests](https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests) | | LIVE-URL | [https://api.kashier.io/v3/payment/instant-settlements/instant-requests](https://api.kashier.io/v3/payment/instant-settlements/instant-requests) | | Method | POST | ### Body parameters [#body-parameters-2] | Key | Description | | -------------- | ------------------------------------------------------------- | | transactionIds | Array of transaction IDs to include in the request. Required. | ```bash curl --location 'https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --data '{ "transactionIds": ["TX-1001", "TX-1002"] }' ``` ### Headers [#headers-3] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-3] ```json { "message": "success", "data": { "id": "d2719f9a-3a36-4f10-9c7b-1f2e3d4c5b6a", "requestId": "ISR-1042", "merchantId": "MID-957-917", "status": "PENDING", "declineReason": null, "totalAmount": 15000, "totalSettlementAmount": 15000, "totalRateFees": 225, "flatFees": 0, "vat": 31.5, "totalFees": 256.5, "netTransferAmount": 14743.5, "transactionsCount": 2, "statusHistory": [ { "status": "PENDING", "at": "2026-06-13T08:00:00.000Z", "by": "merchant@kashier.io" } ], "createdAt": "2026-06-13T08:00:00.000Z", "updatedAt": "2026-06-13T08:00:00.000Z" } } ``` | Field | Description | | ------------------------ | ------------------------------------------------------------------------- | | `data.id` | The request's UUID. Use this or `requestId` to look up the request later. | | `data.requestId` | The request's human-readable ID (e.g. `ISR-1042`). | | `data.status` | See the status table at the top of this page. | | `data.declineReason` | Set when `status` is `DECLINED`; otherwise `null`. | | `data.netTransferAmount` | The amount you'll receive after fees. | | `data.statusHistory` | A log of status changes for this request. | Only transactions that are currently eligible are accepted — if any transaction in `transactionIds` is no longer eligible, the request is rejected with a `400` naming the offending transaction, and no partial request is created. ## List your instant settlement requests [#list-your-instant-settlement-requests] Returns a paginated list of your own instant settlement requests. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests](https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests) | | LIVE-URL | [https://api.kashier.io/v3/payment/instant-settlements/instant-requests](https://api.kashier.io/v3/payment/instant-settlements/instant-requests) | | Method | GET | ### Query parameters [#query-parameters-1] | Key | Description | | --------- | ------------------------------------------------------------------------ | | page | Page number for pagination. Example: `1` | | limit | Records per page. Example: `20` | | status | Filter by status: `PENDING`, `PROCESSING`, `TRANSFERRED`, or `DECLINED`. | | requestId | Filter/search by the human `requestId` (prefix match). | | dateFrom | Inclusive lower bound on transaction date (ISO). | | dateTo | Inclusive upper bound on transaction date (ISO). | ```bash curl --location 'https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests?page=1&limit=20' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-4] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-4] ```json { "message": "success", "data": [ { "id": "d2719f9a-3a36-4f10-9c7b-1f2e3d4c5b6a", "requestId": "ISR-1042", "merchantId": "MID-957-917", "status": "PENDING", "totalSettlementAmount": 15000, "netTransferAmount": 14743.5, "transactionsCount": 2, "createdAt": "2026-06-13T08:00:00.000Z" } ], "pagination": { "total": 1, "limit": 20, "page": 1, "pages": 1 } } ``` ## Get request details [#get-request-details] Fetch the full detail of a single request by its `id` or `requestId`. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests/:id](https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests/:id) | | LIVE-URL | [https://api.kashier.io/v3/payment/instant-settlements/instant-requests/:id](https://api.kashier.io/v3/payment/instant-settlements/instant-requests/:id) | | Method | GET | `:id` accepts either the UUID `id` or the human-readable `requestId` (e.g. `ISR-1042`). ```bash curl --location 'https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests/ISR-1042' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-5] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-5] ```json { "message": "success", "data": { "id": "d2719f9a-3a36-4f10-9c7b-1f2e3d4c5b6a", "requestId": "ISR-1042", "merchantId": "MID-957-917", "status": "PROCESSING", "totalSettlementAmount": 15000, "netTransferAmount": 14743.5, "transactionsCount": 2, "statusHistory": [ { "status": "PENDING", "at": "2026-06-13T08:00:00.000Z" }, { "status": "PROCESSING", "at": "2026-06-13T08:05:00.000Z" } ], "linkedBalanceRecords": [ { "recordId": "ISR-1042-20260615", "accountId": "ACC-39550-436-01", "amount": -10000, "valueDate": "2026-06-15T00:00:00.000Z", "isReflected": false } ] } } ``` | Field | Description | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data.linkedBalanceRecords` | The balance ledger records created for this request, once its early-payout deduction has posted. Empty (`[]`) while `status` is `PENDING`. See [Get balance records](/docs/account-and-balance/balance#get-balance-records-the-ledger) for the general ledger this is drawn from. | | `data.linkedBalanceRecords[].isReflected` | Whether this deduction has been fully reflected in your balance yet. | If the balance ledger is temporarily unavailable when you call this endpoint, the request details still return, but with `linkedBalanceRecords: null` and `linkedBalanceRecordsUnavailable: true`. Retry later to get the linked records. Calling this with an `id`/`requestId` that doesn't belong to you returns a `404`. ## Get request transactions [#get-request-transactions] Returns the transactions that belong to a specific request. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests/:id/transactions](https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests/:id/transactions) | | LIVE-URL | [https://api.kashier.io/v3/payment/instant-settlements/instant-requests/:id/transactions](https://api.kashier.io/v3/payment/instant-settlements/instant-requests/:id/transactions) | | Method | GET | ### Query parameters [#query-parameters-2] | Key | Description | | ----- | ---------------------------------------- | | page | Page number for pagination. Example: `1` | | limit | Records per page. Example: `20` | ```bash curl --location 'https://test-api.kashier.io/v3/payment/instant-settlements/instant-requests/ISR-1042/transactions?page=1&limit=20' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-6] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-6] ```json { "message": "success", "data": [ { "transactionId": "TX-1001", "settlementAmount": 10000, "accountId": "ACC-39550-436-01", "rfsDate": "2026-06-15T00:00:00.000Z", "method": "card" }, { "transactionId": "TX-1002", "settlementAmount": 5000, "accountId": "ACC-39550-436-02", "rfsDate": "2026-06-16T00:00:00.000Z", "method": "wallet" } ], "pagination": { "total": 2, "limit": 20, "page": 1, "pages": 1 } } ``` # Payouts sandbox testing (/docs/payouts/testing) The transfers sandbox lets you test the full payout (transfers) flow on the test environment without sending any real money or calling real providers. When you use the test base URLs, every transfer is simulated end to end. The scenario a transfer follows is chosen from the **recipient number** you send. Pick a number from the tables below to force a specific outcome (success, timeout, invalid recipient, or insufficient funds). Sandbox only runs on the test environment. Use the test base URLs (`https://test-fep.kashier.io`) with your test secret key. Real provider calls never happen here. ## How it works [#how-it-works] The sandbox simulates the transfer at two points, so the whole lifecycle behaves like production without any external call: | Stage | What is simulated | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | Balance check | Insufficient-funds outcomes are decided before any provider is contacted. | | Provider send & reconcile | The send and reconcile calls to the providers (EBC, Axis Pay, OctoCard) are mocked, returning success, a timeout, or an invalid-recipient failure. | Any recipient number that is not listed in the tables below is treated as unknown: the send fails and the transfer ends up FAILED. ## Scenarios [#scenarios] Four scenarios can be triggered. Each test number below maps to exactly one of them. | Scenario | Flow | Final status | | ------------------- | --------------------------------------------------------------------------- | ------------ | | SUCCESS | Send succeeds, reconcile confirms the transfer. | TRANSFERRED | | TIMEOUT | Send succeeds, reconcile returns 504 and is retried until max retries. | IN\_TRANSIT | | INVALID | Send succeeds, reconcile reports an invalid recipient, balance is reversed. | FAILED | | INSUFFICIENT\_FUNDS | Balance check fails before the provider is called. | FAILED | ## Mobile wallet numbers [#mobile-wallet-numbers] Use these as the `recipientNumber` with `method` set to `wallet`. The same numbers work for wallet transfers and instant wallet transfers. | Recipient Number | Scenario | | ---------------- | ------------------- | | 01111111111 | SUCCESS | | 01111111112 | TIMEOUT | | 01111111113 | INVALID | | 01111111114 | INSUFFICIENT\_FUNDS | ## Card numbers [#card-numbers] Use these as the `recipientNumber` with `method` set to `card`. The sandbox matches on the **masked** form exactly as written below — send the asterisks, not a full card number. A full PAN is a different string and matches no scenario, so the transfer is treated as an unknown recipient and ends up FAILED. | Recipient Number | Scenario | | ------------------ | ------------------- | | `5123********2346` | SUCCESS | | `5123********2347` | TIMEOUT | | `5123********2348` | INVALID | | `5123********2349` | INSUFFICIENT\_FUNDS | For `method: card`, `recipientNumber` identifies the recipient only — the card being paid out to is passed separately as `cardToken`, alongside `recipientBank`. Both are required on card transfers. ## Bank account numbers [#bank-account-numbers] Use these as the `recipientNumber` with `method` set to `bank`. | Recipient Number | Scenario | | ---------------- | ------------------- | | 78901234567890 | SUCCESS | | 78901234567891 | TIMEOUT | | 78901234567892 | INVALID | | 78901234567893 | INSUFFICIENT\_FUNDS | ## OctoCard (national ID) numbers [#octocard-national-id-numbers] Use these as the `recipientNumber` for OctoCard transfers identified by national ID. | Recipient Number | Scenario | | ---------------- | ------------------- | | 29901011234567 | SUCCESS | | 29901011234568 | TIMEOUT | | 29901011234569 | INVALID | | 29901011234560 | INSUFFICIENT\_FUNDS | ## Example requests [#example-requests] Send these to the test create-transfer endpoint `https://test-fep.kashier.io/v3/transfers/single` with your test secret key in the Authorization header. Trigger a successful wallet transfer: ```json { "amount": 100, "method": "wallet", "recipientName": "Test User", "merchantTransferId": "sandbox-success-1", "recipientNumber": "01111111111" } ``` Trigger an insufficient-funds failure: ```json { "amount": 100, "method": "wallet", "recipientName": "Test User", "merchantTransferId": "sandbox-insufficient-1", "recipientNumber": "01111111114" } ``` # Payouts webhook (/docs/payouts/webhook) Kashier sends two webhooks during a transfer: 1. **Transfer initiation** — fired when the transfer is accepted. 2. **Transfer final status** — fired when it settles or fails. ## Authentication [#authentication] To verify the integrity of data sent from Kashier, you need to validate the `x-kashier-signature` header using HMAC SHA256. ### Verification process [#verification-process] Extract the `signatureKeys` array from the payload — it lists the keys used to create the signature. Concatenate their values from the payload in the **exact order the array lists them**, as `key_1=value_1&key_2=value_2...`. Do not sort the array, and do not URL-encode the values: use them raw. Generate an HMAC SHA256 hash of that string using your transfer API key as the secret, then compare it with the `x-kashier-signature` header value. If they match, the data is authentic and can be safely processed. For a single transfer the order is fixed as `merchantTransferId`, `method`, `amount`, `merchantId`, `status`, so the signed string looks like this: ```text merchantTransferId=transfer12345&method=wallet&amount=10&merchantId=MID-xxx-xxx&status=INITIATED ``` Batch webhooks use `merchantBatchId`, `batchId`, `method`, `amount`, `merchantId`, `status` — again in that order. The [payment webhook](/docs/webhooks#step-4-verify-the-signature) sorts `signatureKeys` alphabetically, URL-encodes each value, and is keyed by your Payment API Key. The payout webhook does none of that: array order, raw values, transfer API key. A verifier written for one produces the wrong hash for the other. ## Webhook types [#webhook-types] All webhooks include a signature header for security verification: * Header name: `x-kashier-signature` * The signature covers only the fields listed in the payload's `signatureKeys` array, joined as `key=value&key=value` in array order with raw values and hashed with your transfer API key — see [Verification process](#verification-process). ### Transfer initiation webhook [#transfer-initiation-webhook] Sent when a transfer is initially processed by Kashier. | Header | Value | | ------------------- | ---------------------------------------------------------------- | | x-kashier-signature | 1cdc336099a85a8b2a8ffa901cfd55eaa5824e97efd9fc56c34544e9e24bcf7a | #### Payload structure [#payload-structure] ```json { "transferId": "TRS-1007931588", "merchantTransferId": "transfer12345", "amount": 10, "method": "wallet", "status": "INITIATED", "openForReturn": false, "merchantId": "MID-xxx-xxx", "recipientName": "Jhon Doe", "recipientNumber": "01356839512", "businessEmail": "billing@example.com", "storeName": "kashier store", "creatorEmail": "payouts@example.com", "creatorName": "Sara Ahmed", "batch": { "name": "Jhon Doe", "id": "TRS-1007931588", "method": "wallet", "transfersCount": 1 }, "transferResponseCode": "00", "transferResponseMessage": { "en": "success", "ar": "تمت الموافقة" }, "date": "2025-01-15T08:32:37.829Z", "signatureKeys": ["merchantTransferId", "method", "amount", "merchantId", "status"] } ``` On the initiation webhook, `transferResponseCode` is hard-coded to `"00"` with a generic success message, regardless of what the provider returned. It is not a provider result. Only a `TRANSFERRED` status means the funds reached the recipient; the real provider code appears on `IN_TRANSIT`, `TRANSFERRED`, and `FAILED`. ### Transfer final status webhook [#transfer-final-status-webhook] Sent when the transfer reaches its final state (completed or failed). | Header | Value | | ------------------- | ---------------------------------------------------------------- | | x-kashier-signature | 8b41f0d7c25e9a6413ab77f2c0d95e83b6a1c4e70f28d5931ac6e40b7d92f158 | #### Payload structure [#payload-structure-1] ```json { "transferId": "TRS-1007931588", "amount": 10, "method": "wallet", "recipientName": "Jhon Doe", "recipientNumber": "01123439512", "merchantTransferId": "transfer12345", "status": "FAILED", "openForReturn": false, "merchantId": "MID-xxx-xxx", "businessEmail": "billing@example.com", "storeName": "kashier store", "creatorEmail": "payouts@example.com", "creatorName": "Sara Ahmed", "batch": { "name": "Jhon Doe", "id": "TRS-1007931588", "method": "wallet", "transfersCount": 1 }, "transferResponseCode": "k_default", "transferResponseMessage": { "en": "A General Error Occured, please contact support.", "ar": "" }, "date": "2025-01-15T08:32:37.929Z", "signatureKeys": ["merchantTransferId", "method", "amount", "merchantId", "status"] } ``` ## Body description [#body-description] | Key | Value | | ------------------ | ---------------------------------------------------------------------------------------- | | transferId | Kashier's unique identifier for the transfer | | merchantTransferId | Your system's reference number | | amount | Transfer amount | | method | Payment method (e.g., 'wallet') | | status | Current transfer status | | merchantId | Your merchant ID in Kashier's system | | openForReturn | `true` on a `TRANSFERRED` transfer that the receiving wallet or bank can still send back | ## Status values [#status-values] To configure which transfer events actually trigger a webhook delivery — as opposed to the full set of statuses a transfer can reach, listed below — see [manage your webhooks](/docs/webhooks/manage#create-a-webhook). ### For single transfers [#for-single-transfers] | Key | Value | | ----------- | -------------------------------------------------------------------------------------------------------- | | PENDING | Transfer created, balance not yet debited. This is the first status and the one the create call returns. | | INITIATED | Balance debited; the transfer is ready to send to the provider. | | IN\_TRANSIT | Transfer has started but is not yet completed. | | TRANSFERRED | Transfer completed. Final unless `openForReturn` is `true`. | | FAILED | Transfer failed (final status). Balance is reversed. | ### For batch transfers [#for-batch-transfers] | Key | Value | | ---------------------- | --------------------------------------------------------------------------- | | PENDING | Batch created, transfers not yet debited. | | INITIATED | Batch transfer has been created and approved by Kashier but hasn't started. | | IN\_TRANSIT | Batch has started but is not yet completed. | | TRANSFERRED | Transfer is completed (final status). | | PARTIALLY\_TRANSFERRED | Some transfers in the batch were completed, while others failed. | | FAILED | Transfer failed (final status). | ### openForReturn [#openforreturn] A `TRANSFERRED` transfer with `openForReturn: true` is settled but still returnable — the receiving wallet or bank can bounce it back (EBC code `8222`). Treat it as not final and keep reconciling. `openForReturn: false` on `TRANSFERRED` is settled and final. ## Error handling [#error-handling] When a transfer fails, the system provides detailed information to help identify and understand the issue: 1. Status: the transfer status will be updated to "FAILED," indicating the operation was not successful. 2. Error code: the `transferResponseCode` will include a specific code that represents the reason for the failure. 3. Error message: the `transferResponseMessage` will contain a detailed description of the error in both English and Arabic, allowing for better clarity and support across different audiences. # POS API integration (/docs/pos/api-integration) POS API integrations enable developers to interact with the POS system by sending requests and information through APIs. This allows them to initiate sales by transmitting order details or reviewing orders for voiding or refunds. ## Endpoints [#endpoints] | Endpoint | Value | | -------- | --------------------------------------------------------------- | | TEST-URL | `https://test-api.kashier.io/v3/payment/pos/messages/:serialNo` | | LIVE-URL | `https://api.kashier.io/v3/payment/pos/messages/:serialNo` | | Method | POST | ## Parameters [#parameters] | Parameter | Type | Description | Required | | --------- | -------------- | -------------------------------------------------------------------------------- | -------- | | serialNo | Path Parameter | The serial number of the POS terminal. Older docs called this `POSSerialNumber`. | True | ## Headers [#headers] | Key | Description | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Authorization | The Authorization is a secret key used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ## Body structure for a pay request [#body-structure-for-a-pay-request] ```json { "event": "sale", "data": { "message": { "amount": "1", "currency": "EGP", "merchantOrderId": "your orderId", "transactionType": "sale", "printReceipt": true, "metaData": {}, "reconciliation": { "webhookUrl": "https://www.webhookApi.com" } } } } ``` ### Body description [#body-description] | Parameter | Description | Required | | --------------- | ------------------------------------------------------------------------------------------------------------ | --------- | | event | The type of operation sale. | Mandatory | | amount | The amount to be paid through POS. | Mandatory | | currency | The currency the customer will pay with. | Mandatory | | merchantOrderId | The order ID for the payment in your system. | Mandatory | | transactionType | The transaction type of the operation sale. | Mandatory | | printReceipt | Use true to allow the POS to print the receipt. The default value is false. | Optional | | metaData | An object to send extra information related to the transaction as key: value. It must be sent even if empty. | Mandatory | | reconciliation | The endpoint URL where you want to receive Kashier's webhook. | Optional | ## Body structure for a details request [#body-structure-for-a-details-request] ```json { "event": "details", "data": { "message": { "transactionType": "details", "metaData": {}, "details": { "paymentMethod": "card", "merchantOrderId": "your OrderId", "transactionId": "Kashier's transaction ID", "valu": { "type": "" } } } } } ``` ### Body description [#body-description-1] | Parameter | Description | Required | | --------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | | event | The type of operation details. | Mandatory | | transactionType | The transaction type of the operation details. | Mandatory | | metaData | An object to send extra information related to the transaction as key: value. It must be sent even if empty. | Mandatory | | paymentMethod | The payment method of the order you are inquiring about. | Mandatory | | merchantOrderId | The order ID for the order you are inquiring about. | Mandatory | | transactionId | Kashier's transaction ID for the payment you are inquiring about. You can find it in the webhook request sent by Kashier. | Mandatory | | valu.type | The type of ValU credentials your Kashier account uses to process payments through ValU. It is either PF or PSP. | Mandatory only for ValU payments. | # App-to-app integration (/docs/pos/app-to-app) ## Prerequisites [#prerequisites] * Server IP addresses: you must provide the IP addresses of your servers (for both incoming and outgoing calls/requests from your application) so that we can whitelist them with our network provider. * Application (APK) file: you must provide the APK file of your application. We will sign it to ensure it is authorized to run on our machines. ## Download the SDK [#download-the-sdk] [Download the KashierIntegration release (kashierintegration-0.0.7-release.aar)](/assets/legacy/kashierintegration-0.0.7-release.aar) ## Install [#install] 1. Add the AAR file to the `libs` folder. Convert your app from Android to Project view, then navigate to the `app > libs` folder. Adding the AAR file 2. Add the dependencies. You need to include the dependency below in your `build.gradle`. Adding dependencies Now you're ready to sync your project. ## Usage [#usage] The SDK provides two functions to initiate payment: `startSale()` and `startDetails()`. 1. Import the following classes. Import the following classes 2. Implement `KashierIntegrationCallback` and `KashierIntegrationFactory`. Kotlin implementation of KashierIntegrationCallback Java implementation of KashierIntegrationCallback 3. To make a sale transaction, call the `startSale` method and pass the following parameters: | Parameter | Description | | --------------- | --------------------------------------------------------------------------------------------- | | amount | The amount of the transaction sale. | | currency | EGP, USD, GBP, EUR. | | merchantOrderId | Your integration ID. | | paymentMethod | card, wallet, installment, valu. | | printReceipt | true: Allow auto printing for the receipt; false: Do not allow auto printing for the receipt. | | metaData | mapOf(String(), Any()): Any data you want to pass through the integration. | 4. To get details of any transaction, call the `startDetails` method using the transaction ID. You need to pass the same parameters as in step 3, plus one additional parameter: | Parameter | Description | | --------- | -------------------------- | | trxId | The ID of the transaction. | Kotlin transaction Java transaction 5. As a result of the above code, you should expect values returned in your response to update your backend. Kotlin response Java response 6. Response codes: | Response Code | Description | | ------------- | --------------------- | | 500 | Transaction canceled. | | 400 | Transaction failure. | | 200 | Transaction success. | # In-person payments (/docs/pos) Drive Kashier POS terminals from your own software. There are two integration paths — pick the one that matches where your code runs. ## App-to-app integration [#app-to-app-integration] Use [app-to-app](/docs/pos/app-to-app) when your own Android app runs on the Kashier terminal itself. Your app launches the Kashier payment app through an Android SDK (the KashierIntegration `.aar` library) and receives the result in a callback. The SDK exposes `startSale()` to take a payment — with control over currency (EGP, USD, GBP, EUR), payment method (card, wallet, installment, valu), and receipt printing — and `startDetails()` to fetch a transaction's details. Before you can use it, you must: * Provide your server IP addresses so Kashier can whitelist them with the network provider. * Provide your APK so Kashier can sign it to run on the terminals. * Add the `.aar` file to your Android project and sync the dependency. ## POS API integration [#pos-api-integration] Use the [POS API](/docs/pos/api-integration) when the terminal runs the standard Kashier payment app and you trigger payments from your backend. You POST a sale or details request to an endpoint addressed by the terminal's serial number, authorized with the secret key from your Kashier dashboard. Pass a webhook URL in the sale request to receive the result. The API answers on both the test and live hosts. Both paths support sale and transaction-details operations. # Use these docs with AI (/docs/resources/ai-tools) These docs are built to be read by AI tools as easily as by people. Point your assistant at any of the following: ## Machine-readable endpoints [#machine-readable-endpoints] | What | URL | Use it for | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | Docs index for LLMs | [/llms.txt](/llms.txt) | Give an agent a map of every page with descriptions | | Full docs as one file | [/llms-full.txt](/llms-full.txt) | Paste the entire documentation into a large-context model | | Any page as Markdown | append `.md` to any docs URL, e.g. [/docs/accept-payments/payment-sessions.md](/docs/accept-payments/payment-sessions.md) | Feed a single topic to a model without HTML noise | | OpenAPI 3.1 specification | [/openapi.yaml](/openapi.yaml) | Code generation, API clients, agent tool definitions | ## On every page [#on-every-page] Each docs page has a **Copy Markdown** button and an **Open** menu that sends the page straight to ChatGPT or Claude — useful when you want to ask questions about one integration. ## Suggested prompts [#suggested-prompts] Working in Cursor, Claude Code, or another coding agent? Try: ```text Read https://developers.kashier.io/llms.txt, then fetch the pages relevant to accepting a card payment with Kashier and implement it in my checkout flow. Use the test environment (test-api.kashier.io) and my test keys from .env. ``` ```text Generate a typed API client for Kashier from https://developers.kashier.io/openapi.yaml. Payments only. ``` ## Notes for agents [#notes-for-agents] * Authentication is two headers: `Authorization` (the merchant secret key, raw value — not a Bearer token) and `api-key`. See [API keys](/docs/get-started/api-keys). * Test and live are separate environments; test base URLs are prefixed `test-`. Never use live keys while developing. * Amounts are strings in EGP for payment sessions (for example `"100.00"`). # Resources (/docs/resources) Tools that go with these docs — pick what fits your workflow. # Postman collection (/docs/resources/postman) Kashier provides some [Postman collections](https://www.postman.com/collection) to help test and understand the API: [Download the Kashier API Postman collection]() ## How to import a collection into Postman [#how-to-import-a-collection-into-postman] 1. Navigate to File > Import. Postman File > Import menu 2. Choose the import file method. Postman import file method 3. Choose the correct file to import and click Open. Postman will automatically import the file. Choose file to import 4. Click on the Import button. Postman Import button 5. Navigate to File > New Runner Tab. 6. The Collection Runner tab will open. 7. Now you can explore the Kashier API endpoints. # Rate limits (/docs/resources/rate-limits) ## Direct API (FEP) [#direct-api-fep] Checkout requests (`POST /v3/orders` on `fep.kashier.io`, i.e. creating a payment) are limited to **1000 requests per minute**, on the **live/production environment only**. There is no rate limit on the test environment. This limit is confirmed specifically for the checkout endpoint — rate-limit behavior for other `fep.kashier.io` routes is not confirmed, so don't assume the same figure applies elsewhere on the domain. The limit is applied **per IP address**. If several of your services share one outbound IP, or you run behind a shared NAT or proxy, they share a single bucket — so the effective ceiling for any one service is lower than 1000/min. If you exceed the limit, further requests from that IP are rejected. Kashier has not published the reset behaviour, the response shape, or a `Retry-After` header for this limit — build your integration to handle request failures gracefully and avoid bursting requests, rather than relying on a specific error format or reset timing. ## Dashboard API [#dashboard-api] One Dashboard API endpoint, `POST /v2/paymentRequest/share`, is rate-limited. Limits for this and other Dashboard API (`api.kashier.io`) endpoints are not yet published — check with your account manager if you need guaranteed throughput. # Support (/docs/resources/support) * **Integration questions** — contact the Kashier team through [kashier.io/contact](https://www.kashier.io/contact). * **Dashboard and account help** — reach support from your [merchant dashboard](https://merchant.kashier.io). * **Plugin issues** — check the guide for your platform under [E-commerce plugins](/docs/plugins) first; each page lists its requirements. When you report an integration issue, include your merchant ID (`MID-...`), whether you are in test or live mode, the endpoint you are calling, and the full response body you received — it makes diagnosis much faster. Never share your secret key or API key in a support message. # Webhooks (/docs/webhooks) Kashier notifies your server about payment and refund events by sending POST requests to a URL you control. Each request carries an Event object with the full transaction details — see [Webhook payloads](/docs/webhooks/payloads) for the event types and every field Kashier sends. Use webhooks for post-payment commerce events such as sending custom email receipts, fulfilling orders, or updating your database. They are also the only way to receive automatic updates for payment methods that confirm asynchronously. ## Prerequisites [#prerequisites] Before you configure a webhook, make sure you have: * **A publicly accessible server endpoint** that can receive unauthenticated POST requests — Kashier calls it directly over the internet, so `localhost` or anything behind auth won't work. * **Your Payment API Key**, found in your dashboard under the [Integrations section](https://merchant.kashier.io/en/integrations) — you'll use it in Step 4 to verify that incoming requests really came from Kashier. * **A read of [Webhook payloads](/docs/webhooks/payloads)**, so you know which event types and fields to expect in the request body. ## Step 1: Build your endpoint [#step-1-build-your-endpoint] Add a new route to your server and make sure it's publicly accessible so Kashier can send unauthenticated POST requests to it. Kashier sends the event data as JSON in the request body: an Event object whose `event` field names the transaction operation (`pay`, `authorize`, `capture`, `refund`, `partial_refund`, `void`, `reject`, or `reversal`) and whose `data` payload contains the transaction details. See [Webhook payloads](/docs/webhooks/payloads) for the full payload and field notes. Kashier also sends a webhook when an operation **fails** — same `event` value, with `"status": "FAILURE"` in `data`. Branch on `data.status` (`SUCCESS`, `FAILURE`, or `PENDING`), never on `event` alone. `status` is the transaction status, not the order status. ## Step 2: Point Kashier at your endpoint [#step-2-point-kashier-at-your-endpoint] There are two ways an event reaches your endpoint, and they work together — if both apply to a payment, Kashier delivers to both independently. **Configure a webhook.** [Webhook management](/docs/webhooks/manage) is where you register endpoints, and it's the way to set webhooks up. Each webhook has a name, a URL, optional custom headers, a mode (`test` or `live`), and the list of event types it's subscribed to — so a production endpoint can take just `pay` and `refund` while a separate staging endpoint listens on `test` mode. Delivery records, resend, and test-send live in the same module — see [Webhook management](/docs/webhooks/manage) for which of them are available today. You don't need to re-register it. A single stored webhook URL configured before webhook management existed is migrated into the module for you and keeps receiving what it received before — see [If you used the old single-URL webhook](/docs/webhooks/manage#if-you-used-the-old-single-url-webhook). **Pass a per-request destination.** Payment-creating calls also accept a `serverWebhook` field — [payment sessions](/docs/accept-payments/payment-sessions), the [Direct API card form](/docs/direct-api/card-form), and [recurring payments](/docs/accept-payments/recurring) each take one. That URL applies to that payment only. Kashier records and delivers to it under the same mode as the payment, without it becoming a configured webhook, and without event filtering — a per-request destination receives every event that payment produces. Use it when the destination genuinely varies per transaction, for example a platform routing each order's notifications to a different vendor. Payouts have no per-request equivalent; transfer events only go to configured webhooks. ## Step 3: Respond to events [#step-3-respond-to-events] Respond with `200` as soon as you receive the event. Kashier treats HTTP `200` and HTTP `409 Conflict` as acknowledged — return `409` for an event you have already processed. Any other status code counts as unacknowledged, as does any response slower than the 30-second delivery timeout. Kashier retries an unacknowledged event up to 10 times, backing off `2 minutes` → `10 minutes` → `30 minutes` → `1 hour` → `2 hours` → `4 hours`, then every `4 hours`. Don't send a response body — it's discarded, and only the status code is read. If your handler starts a long-running task, return `200` first and do the work afterwards; otherwise delivery times out and the event is sent again. Make your handler safe to run twice — see [Idempotency](#idempotency). ## Step 4: Verify the signature [#step-4-verify-the-signature] Kashier signs every webhook request with SHA256 HMAC so you can verify it wasn't tampered with in transit. To generate the signature, sort the elements of the `signatureKeys` array in the `data` payload alphabetically. Each element is a key of the `data` object. Select those keys and their values from the received `data` object, then build the signature payload: ```text amount=1&channel=online%20%7C%20e-commerce¤cy=EGP&kashierOrderId=9ad06b17-755b-4e21-9774-aff3e2726ac9&merchantOrderId=1653481557813&method=card&orderReference=TEST-ORD-38855&status=SUCCESS&transactionId=TX-249893963&transactionResponseCode=00 ``` Hash the signature payload with your Payment API Key, then compare your result with the `x-kashier-signature` request header. If both are equal, the data is safe to save and use in your system. You must use the Payment API Key that you used to create the [Payment Hash](/docs/direct-api/hashing). You can find the Payment API Key in your dashboard under the [Integrations section](https://merchant.kashier.io/en/integrations). Ensure that only the values of the keys are URL-encoded, not the entire string. This means each value should be properly encoded before concatenating the key-value pairs to form the signature payload. ### How the signature is computed, step by step [#how-the-signature-is-computed-step-by-step] Here's the computation above traced through with concrete values, so you can check your own implementation against a known-good result. **1. Start from the received `data` payload.** Say Kashier's request body contains this `data` object (trimmed to the signature-relevant fields — see [Webhook payloads](/docs/webhooks/payloads) for the full shape): ```json { "amount": 1, "channel": "online | e-commerce", "currency": "EGP", "kashierOrderId": "9ad06b17-755b-4e21-9774-aff3e2726ac9", "merchantOrderId": "1653481557813", "method": "card", "orderReference": "TEST-ORD-38855", "status": "SUCCESS", "transactionId": "TX-249893963", "transactionResponseCode": "00", "signatureKeys": [ "amount", "channel", "currency", "kashierOrderId", "merchantOrderId", "method", "orderReference", "status", "transactionId", "transactionResponseCode" ] } ``` **2. Sort `signatureKeys` alphabetically.** In this example it's already sorted: `amount`, `channel`, `currency`, `kashierOrderId`, `merchantOrderId`, `method`, `orderReference`, `status`, `transactionId`, `transactionResponseCode`. **3. Look up each key's value in `data`, and URL-encode only the value.** Only `channel`'s value contains characters that need encoding — the space becomes `%20` and the `|` becomes `%7C`: | Key | Value | URL-encoded value | | ------------------------- | -------------------------------------- | -------------------------------------- | | `amount` | `1` | `1` | | `channel` | `online \| e-commerce` | `online%20%7C%20e-commerce` | | `currency` | `EGP` | `EGP` | | `kashierOrderId` | `9ad06b17-755b-4e21-9774-aff3e2726ac9` | `9ad06b17-755b-4e21-9774-aff3e2726ac9` | | `merchantOrderId` | `1653481557813` | `1653481557813` | | `method` | `card` | `card` | | `orderReference` | `TEST-ORD-38855` | `TEST-ORD-38855` | | `status` | `SUCCESS` | `SUCCESS` | | `transactionId` | `TX-249893963` | `TX-249893963` | | `transactionResponseCode` | `00` | `00` | **4. Join each pair with `=` and all pairs with `&`.** That produces exactly the signature payload string shown above: ```text amount=1&channel=online%20%7C%20e-commerce¤cy=EGP&kashierOrderId=9ad06b17-755b-4e21-9774-aff3e2726ac9&merchantOrderId=1653481557813&method=card&orderReference=TEST-ORD-38855&status=SUCCESS&transactionId=TX-249893963&transactionResponseCode=00 ``` **5. HMAC-SHA256 that string, keyed with your Payment API Key.** This is the same credential used for the [Payment Hash](/docs/direct-api/hashing) — not your Secret Key. If the "Payment API Key" vs. "Secret Key" vs. "MID" terminology is unclear, see [Credentials at a glance](/docs/get-started/api-keys#credentials-at-a-glance) for how each maps to its actual header or field name. Using the same illustrative key (`11111`) as the [Request hashing](/docs/direct-api/hashing) examples, the resulting hex digest is: ```text 9610477b2255b2a8ef84fd89adfaa5f1305ff9c20324205851890f1ea03109f4 ``` **6. Compare the digest with the `x-kashier-signature` request header.** If they're equal (byte-for-byte, case-insensitive hex comparison), the request is authentic and the `data` payload is safe to save and use — this is exactly what the `if (kashierSignature === signature)` check does in the code samples below. ```js const app = require('express')(); // Use body-parser to retrieve the raw body as a buffer const bodyParser = require('body-parser'); const crypto = require('crypto'); const queryString = require('query-string'); const _ = require('underscore'); router.post('/', (req, res) => { const { data, event } = req.body; data.signatureKeys.sort(); const objectSignaturePayload = _.pick(data, data.signatureKeys); const signaturePayload = queryString.stringify(objectSignaturePayload); const signature = crypto .createHmac('sha256', PaymentApiKey) .update(signaturePayload) .digest('hex'); const kashierSignature = req.header('x-kashier-signature'); if (kashierSignature === signature) { console.log('valid signature'); } else { console.log('invalid signature'); } }); app.listen(8000, () => console.log('Running on port 8000')); ``` ```php ``` ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Web; using System.Web.Mvc; public class SignatureValidator { public bool ValidateSignature(string requestBody, string receivedSignature, string secretKey) { string path = ""; using (JsonDocument document = JsonDocument.Parse(requestBody)) { JsonElement data = document.RootElement.GetProperty("data"); List signatureKeys = data.GetProperty("signatureKeys") .EnumerateArray() .Select(key => key.GetString()) .OrderBy(key => key, StringComparer.Ordinal) .ToList(); foreach (string key in signatureKeys) { string value = data.GetProperty(key).ToString(); path = path + "&" + key + "=" + Uri.EscapeDataString(value); } } string message = path.Substring(1); System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding(); byte[] keyByte = encoding.GetBytes(secretKey); byte[] messageBytes = encoding.GetBytes(message); string computedSignature; using (HMACSHA256 hmacsha256 = new HMACSHA256(keyByte)) { byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); computedSignature = BitConverter.ToString(hashmessage).Replace("-", "").ToLowerInvariant(); } return receivedSignature != null && receivedSignature.Equals(computedSignature, StringComparison.OrdinalIgnoreCase); } } public class WebhookController : Controller { [HttpPost] public ActionResult ReceiveWebhook() { string requestBody = string.Empty; using (var reader = new System.IO.StreamReader(Request.InputStream)) { requestBody = reader.ReadToEnd(); } string receivedSignature = Request.Headers["x-kashier-signature"]; string secretKey = "YOUR_API_KEY"; SignatureValidator validator = new SignatureValidator(); bool isValid = validator.ValidateSignature(requestBody, receivedSignature, secretKey); if (isValid) { // Perform necessary actions return Json(new { message = "Valid signature" }, JsonRequestBehavior.AllowGet); } else { return Json(new { message = "Invalid signature" }, JsonRequestBehavior.AllowGet); } } } ``` ## Idempotency [#idempotency] Kashier de-dupes delivery on `{transactionId}::{webhookUrl}::{status}`, but the same `transactionId` and `status` can still reach you more than once — retries, races, and replays all produce repeats. Design the handler for it: * Key your processing on `transactionId` + `status`, not on receipt of the request. * Return `200` fast, before any downstream work. * Return `409` for an event you have already processed. Kashier counts `409` as delivered and stops retrying. * Never treat a repeat as a second payment, refund, or fulfilment. Order-level replays are visible in the payload: a repeated attempt on an already-paid order carries the `ORDER_PAID_BEFORE` code, and a replayed notification arrives as `event: "idempotency"`. Reconcile both against the order you already have rather than creating a new one. # Webhook management (/docs/webhooks/manage) Kashier's webhook management module lets you configure **multiple named webhooks** per merchant instead of a single fixed URL. Each webhook is scoped to a mode (`test` or `live`) and subscribed to a specific set of event types, so different endpoints can receive different slices of your traffic — for example, a production endpoint that only cares about `refund` and transfer status changes, and a separate test endpoint your staging environment listens to. This page covers creating and managing webhooks, looking up delivery records for a transaction or transfer, resending a delivery, and sending a test payload to verify an endpoint is reachable. For the shape of the event payload itself and signature verification, see [Webhook payloads](/docs/webhooks/payloads) and the [Webhooks guide](/docs/webhooks). All endpoints on this page are merchant-authenticated — send your secret key in the `Authorization` header, the same as everywhere else in the API. Reading delivery records requires the `webhook.view_webhook` permission, and resending a delivery requires `webhook.resend_webhook`. These are dedicated webhook permissions rather than a side effect of your transaction or transfer access, and they're enforced for merchant users, not only for Kashier staff — a request without them is rejected with `403` even when the key itself is valid. Permission changes are cached for roughly 24 hours, so a newly granted permission may not take effect straight away. The CRUD endpoints (list, create, update, delete) are merchant-gated too, but which privilege they check wasn't pinned down at design time — if one returns `403` on a valid key, ask your Kashier contact which permission your user is missing. ## If you used the old single-URL webhook [#if-you-used-the-old-single-url-webhook] If your account was set up with Kashier's older single stored webhook URL, there's nothing to re-create. That URL is migrated into webhook management for you, so [List your webhooks](#list-your-webhooks) returns it as a webhook named `Legacy webhook`, subscribed to every event type, with `isActive` carrying over whether the old webhook was enabled and `mode` derived from your account's current mode. It keeps receiving exactly what it received before the migration. From there it's an ordinary webhook. Rename it, narrow its `events` to the ones you actually handle, add custom `headers`, or switch it off with [Update a webhook](#update-a-webhook). If you want separate test and live destinations, create a second webhook for the other mode rather than flipping this one's `mode` back and forth. ## List your webhooks [#list-your-webhooks] Returns every webhook configured for your merchant, across both modes. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/webhooks](https://test-api.kashier.io/v2/webhooks) | | LIVE-URL | [https://api.kashier.io/v2/webhooks](https://api.kashier.io/v2/webhooks) | | Method | GET | Full parameter and response reference → [List your webhooks](/docs/api-reference/webhooks/listWebhooks). ```bash curl --location 'https://test-api.kashier.io/v2/webhooks' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response] ```json [ { "_id": "66a1f2c3e4b0a1234567890a", "merchantId": "MID-10293", "name": "Orders production endpoint", "url": "https://shop.example.com/kashier/webhook", "headers": { "X-Shop-Token": "whsec_live_abc123", "X-Env": "production" }, "events": ["pay", "refund", "void", "TRANSFERRED"], "mode": "live", "isActive": true, "createdAt": "2026-06-22T12:00:00.000Z", "updatedAt": "2026-06-22T12:00:00.000Z" } ] ``` | Field | Description | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `_id` | Unique identifier for the webhook — this is the `webhookId` used in [Update a webhook](#update-a-webhook) and [Delete a webhook](#delete-a-webhook). | | `merchantId` | The merchant the webhook belongs to. | | `name` | Your label for the webhook. | | `url` | The endpoint Kashier delivers to. | | `headers` | Custom headers Kashier sends with every delivery to this webhook, as a flat string-to-string map. | | `events` | The event types this webhook is subscribed to — see [Create a webhook](#create-a-webhook) for the full list. | | `mode` | `test` or `live` — the webhook only receives events emitted in this mode. | | `isActive` | Whether the webhook currently receives deliveries. | | `createdAt` / `updatedAt` | Timestamps. | ## Create a webhook [#create-a-webhook] Creates a new webhook for your merchant. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/webhooks](https://test-api.kashier.io/v2/webhooks) | | LIVE-URL | [https://api.kashier.io/v2/webhooks](https://api.kashier.io/v2/webhooks) | | Method | POST | Full parameter and response reference → [Create a webhook](/docs/api-reference/webhooks/createWebhook). ### Body parameters [#body-parameters] | Key | Description | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | Your label for the webhook. | | url | The endpoint Kashier delivers to. Must be a public `https` URL — non-`https` schemes, `localhost`, loopback, link-local, and private (RFC-1918) addresses are rejected. | | headers | Optional. Custom headers to send with every delivery to this webhook, as a flat string-to-string map. Defaults to `{}`. | | events | Array of event types to subscribe this webhook to — see the table below. | | mode | `test` or `live`. The webhook only receives events emitted in this mode. | | isActive | Optional. Whether the webhook is enabled. Defaults to `true`. | You can configure up to 5 webhooks per mode (5 `test` + 5 `live` = 10 total). Creating a 6th webhook for a mode that's already at the limit is rejected. This limit is taken from Kashier's webhook-management design documentation rather than a live API response, and is flagged there as still-to-confirm (per-mode vs. global was open at design time). If a 6th webhook is unexpectedly accepted or rejected, treat your account's actual behavior as authoritative. `events` accepts any of the following values: | Category | Values | | ---------------------- | ----------------------------------------------------------- | | Transaction operations | `pay`, `authorize`, `capture`, `refund`, `void`, `reversal` | | Transfer statuses | `INITIATED`, `IN_TRANSIT`, `TRANSFERRED`, `FAILED` | The mixed casing is deliberate — each value matches the producing service's own literal, so transaction operations are lowercase and transfer statuses uppercase. Send them exactly as written. The subscribable list holds base operations only, and matching is exact. A transaction whose operation is `partial_refund` does not match a `refund` subscription, so a webhook subscribed to `refund` won't be delivered that transaction. A [per-request destination](/docs/webhooks#step-2-point-kashier-at-your-endpoint) is not event-filtered and still receives it. The same applies to any other operation outside the list above. The subscribable list is narrower than the full transfer lifecycle. `PENDING` (and, for batches, `PARTIALLY_TRANSFERRED`) appear in the [payout status values](/docs/payouts/webhook#status-values) but aren't subscribable here, so a transfer entering one of them won't trigger a delivery. If you need a status that isn't listed, use the create-webhook "Try it" panel below to confirm the accepted set against your own account before assuming it's unavailable. Transaction events and transfer events are signed with **different keys and different serializations**, so a webhook subscribed to both — like the example below — needs two verifiers and has to pick one based on the event it received. * **Transaction events** are signed with your **Payment API Key** for the webhook's mode: your test key signs `test`-mode deliveries, your live key signs `live` ones. The signed string sorts `signatureKeys` alphabetically and URL-encodes each value. See [signature verification](/docs/webhooks#step-4-verify-the-signature). * **Transfer events** are signed with your **Transfer API Key**, over that payload's own `signatureKeys` in array order with **raw, un-encoded** values. See [Payout webhooks](/docs/payouts/webhook#verification-process). Reusing one verifier for both produces the wrong hash for one of them. ```bash curl --location 'https://test-api.kashier.io/v2/webhooks' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --data '{ "name": "Orders staging endpoint", "url": "https://your-website.com/kashier-webhook", "headers": { "X-Shop-Token": "replace-with-your-own-token", "X-Env": "staging" }, "events": ["pay", "refund", "void", "TRANSFERRED"], "mode": "test", "isActive": true }' ``` For a production webhook, send the same body with `"mode": "live"` to `https://api.kashier.io/v2/webhooks` using your live secret key — keep `mode` and the host you call in step rather than creating a `live` webhook from the test host. Unlike the read-only panels on this page, sending this one leaves persistent state on the account whose keys you saved: a webhook that stays there until you delete it, and that counts against the 5-per-mode limit above. The body below is deliberately defanged — `mode` is `test`, `isActive` is `false`, and the URL is a placeholder — so nothing is ever delivered anywhere. Delete it afterwards with [Delete a webhook](#delete-a-webhook), using the `_id` from the response. ### Headers [#headers-1] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-1] Returns the created webhook, in the same shape as an entry in [List your webhooks](#list-your-webhooks). ## Update a webhook [#update-a-webhook] Updates an existing webhook. Send only the fields you want to change. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/webhooks/:webhookId](https://test-api.kashier.io/v2/webhooks/:webhookId) | | LIVE-URL | [https://api.kashier.io/v2/webhooks/:webhookId](https://api.kashier.io/v2/webhooks/:webhookId) | | Method | PUT | Full parameter and response reference → [Update a webhook](/docs/api-reference/webhooks/updateWebhook). ### Body parameters [#body-parameters-1] Same fields as [Create a webhook](#create-a-webhook) — `name`, `url`, `headers`, `events`, `mode`, `isActive` — all optional on update. The `https`-only URL rule applies here too. Changing `mode` re-checks the 5-per-mode limit against the target mode. ```bash curl --location --request PUT 'https://test-api.kashier.io/v2/webhooks/:webhookId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' \ --header 'Content-Type: application/json' \ --data '{ "isActive": false }' ``` ### Headers [#headers-2] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-2] Returns the updated webhook, in the same shape as an entry in [List your webhooks](#list-your-webhooks). ## Delete a webhook [#delete-a-webhook] Removes a webhook. There's no undo — deliveries already recorded against it stay visible in delivery records. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/webhooks/:webhookId](https://test-api.kashier.io/v2/webhooks/:webhookId) | | LIVE-URL | [https://api.kashier.io/v2/webhooks/:webhookId](https://api.kashier.io/v2/webhooks/:webhookId) | | Method | DELETE | Full parameter and response reference → [Delete a webhook](/docs/api-reference/webhooks/deleteWebhook). ```bash curl --location --request DELETE 'https://test-api.kashier.io/v2/webhooks/:webhookId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-3] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ## Get delivery records for a transaction [#get-delivery-records-for-a-transaction] Returns the delivery log for a given transaction — every webhook attempt made for it, across all your configured webhooks, plus any per-session webhook you passed directly on that transaction. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/webhooks/records/transaction/:transactionId](https://test-api.kashier.io/v2/webhooks/records/transaction/:transactionId) | | LIVE-URL | [https://api.kashier.io/v2/webhooks/records/transaction/:transactionId](https://api.kashier.io/v2/webhooks/records/transaction/:transactionId) | | Method | GET | Full parameter and response reference → [Get delivery records for a transaction](/docs/api-reference/webhooks/getWebhookRecordsForTransaction). ```bash curl --location 'https://test-api.kashier.io/v2/webhooks/records/transaction/:transactionId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` This route isn't registered on `test-api.kashier.io` today — calling it there returns a `404` rather than a records payload, while the webhook CRUD endpoints on the same router answer normally. It's documented here because it's designed and ticketed, but there's no "Try it" panel for it until it ships. Check with your Kashier contact before building against it. ### Headers [#headers-4] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-3] ```json { "success": true, "body": [ { "resourceType": "transaction", "resourceId": "TX-249893963", "webhookId": "66a1f2c3e4b0a1234567890a", "eventType": "pay", "mode": "live", "url": "https://shop.example.com/kashier/webhook", "status": "delivered", "attempts": 1, "payload": { "...": "allow-listed fields only" }, "responses": [ { "status": 200, "body": "OK", "date": "2026-06-22T12:00:01.000Z" } ], "isServerWebhook": false, "isTest": false, "createdAt": "2026-06-22T12:00:00.000Z", "updatedAt": "2026-06-22T12:00:01.000Z" } ] } ``` | Field | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `resourceType` | `transaction` for records returned here. | | `resourceId` | The transaction ID this delivery relates to. | | `webhookId` | The webhook this delivery came from, or `null` if it was a per-session webhook passed directly on the transaction. | | `eventType` | The event that triggered this delivery. | | `mode` | `test` or `live`. | | `url` | The destination URL the delivery was sent to. | | `status` | Delivery status — `pending`, `delivered`, `retrying`, or `failed`. A record sits in `retrying` between backoff attempts and only reaches `failed` once the attempt cap is exhausted. | | `attempts` | Number of delivery attempts recorded. | | `payload` | The request body sent, projected to a fixed, always-visible field set. Custom `headers` you configured are never included in this response. | | `responses` | One entry per attempt: the receiving endpoint's `status`, `body`, and `date`. | | `isServerWebhook` | `true` if this delivery went to a per-session webhook rather than a configured webhook. | | `isTest` | `true` if this record came from [Test a webhook](#test-a-webhook) rather than a real delivery. | ## Get delivery records for a transfer [#get-delivery-records-for-a-transfer] Same records lookup as above, scoped to a transfer instead of a transaction. | Endpoint | Value | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/webhooks/records/transfer/:transferId](https://test-api.kashier.io/v2/webhooks/records/transfer/:transferId) | | LIVE-URL | [https://api.kashier.io/v2/webhooks/records/transfer/:transferId](https://api.kashier.io/v2/webhooks/records/transfer/:transferId) | | Method | GET | Full parameter and response reference → [Get delivery records for a transfer](/docs/api-reference/webhooks/getWebhookRecordsForTransfer). ```bash curl --location 'https://test-api.kashier.io/v2/webhooks/records/transfer/:transferId' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` Like its transaction counterpart, this route isn't registered on `test-api.kashier.io` today — calling it there returns a `404`. It's documented because it's designed and ticketed, but there's no "Try it" panel for it until it ships. ### Headers [#headers-5] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-4] Same shape as [Get delivery records for a transaction](#get-delivery-records-for-a-transaction), except `resourceType` is `transfer`, `resourceId` is the transfer ID, and `eventType` is one of the transfer status values (`INITIATED`, `IN_TRANSIT`, `TRANSFERRED`, `FAILED`). ## Resend a delivery [#resend-a-delivery] Re-sends a previously recorded delivery — the original stored request, byte-for-byte, including its original signature — to the same URL. Useful after fixing an endpoint that was down or returning errors. The outcome is written to a new record; the original record is never modified, so your delivery history stays intact. A record can be resent from any status, including `delivered`. The endpoint takes no request body — everything it needs comes from `recordId` in the path. | Endpoint | Value | | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | | TEST-URL | [https://test-api.kashier.io/v2/webhooks/records/:recordId/resend](https://test-api.kashier.io/v2/webhooks/records/:recordId/resend) | | LIVE-URL | [https://api.kashier.io/v2/webhooks/records/:recordId/resend](https://api.kashier.io/v2/webhooks/records/:recordId/resend) | | Method | POST | Full parameter and response reference → [Resend a delivery](/docs/api-reference/webhooks/resendWebhookRecord). Resending the same record again within 30 seconds returns `429`. Wait for the cooldown to clear before retrying. ```bash curl --location --request POST 'https://test-api.kashier.io/v2/webhooks/records/:recordId/resend' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` ### Headers [#headers-6] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-5] ```json { "recordId": "66a20a1ce4b0a1234567891b", "clonedFromRecordId": "66a1f2c3e4b0a1234567890c", "status": "delivered", "attempts": 1, "lastResponseStatus": 200 } ``` | Field | Description | | -------------------- | ---------------------------------------------------------------------------- | | `recordId` | ID of the new delivery record created for this resend. | | `clonedFromRecordId` | ID of the original record this resend replayed. | | `status` | Outcome of the resend, e.g. `delivered` or `failed`. | | `attempts` | Attempts recorded on the new record (always `1` immediately after a resend). | | `lastResponseStatus` | HTTP status your endpoint returned for the resend attempt. | ## Test a webhook [#test-a-webhook] Sends a signed sample payload to a webhook's configured URL so you can confirm the endpoint is reachable and your signature verification works — without waiting for a real event. The test delivery is recorded like any other delivery, but flagged `isTest: true` so it's distinguishable in your delivery history. | Endpoint | Value | | -------- | ---------------------------------------------------------------------------------------------------- | | TEST-URL | [https://test-api.kashier.io/v2/webhooks/:id/test](https://test-api.kashier.io/v2/webhooks/:id/test) | | LIVE-URL | [https://api.kashier.io/v2/webhooks/:id/test](https://api.kashier.io/v2/webhooks/:id/test) | | Method | POST | Full parameter and response reference → [Test a webhook](/docs/api-reference/webhooks/testWebhook). ```bash curl --location --request POST 'https://test-api.kashier.io/v2/webhooks/:id/test' \ --header 'Authorization: YOUR_TEST_SECRET_KEY' ``` This route isn't registered on `test-api.kashier.io` today — calling it there returns a `404`, so there's no "Try it" panel for it. Until it ships, verify an endpoint by running a real test-mode payment against it and reading the outcome from your own server logs. ### Headers [#headers-7] | Key | Description | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). | ### Response [#response-6] ```json { "success": true, "statusCode": 200, "responseBody": "OK", "latencyMs": 184 } ``` | Field | Description | | -------------- | ------------------------------------------------------- | | `success` | `true` if your endpoint returned a 2xx status. | | `statusCode` | HTTP status your endpoint returned. | | `responseBody` | A truncated snippet of your endpoint's response body. | | `latencyMs` | Round-trip time for the test delivery, in milliseconds. | ## Next steps [#next-steps] # Webhook payloads (/docs/webhooks/payloads) Field-level reference for the event objects Kashier sends. For endpoint setup, acknowledgement, and signature verification, see the [Webhooks guide](/docs/webhooks). ## Events [#events] The `event` field is the transaction operation, set verbatim. Events: * `pay` * `authorize` * `capture` * `refund` * `partial_refund` * `void` * `reject` * `reversal` These are the values Kashier sets on a delivered event. The set you can **subscribe** a configured webhook to is narrower — see [Create a webhook](/docs/webhooks/manage#create-a-webhook). `event` is not a success signal. A webhook also fires when the operation fails: the `event` is unchanged and `data.status` is `FAILURE`. Read `data.status` — `SUCCESS`, `FAILURE`, or `PENDING` — to decide the outcome. Never treat the arrival of a `pay` event as a completed payment. `status` is the transaction status, not the order status. ## Event payload [#event-payload] Each event is structured as an Event object with a `data` payload that contains the transaction details. ```json { "event": "pay", "data": { "merchantOrderId": "1642935044835", "kashierOrderId": "efb3d440-e3bf-4c86-b98e-c7bb1cbbcca1", "orderReference": "TEST-ORD-33581", "transactionId": "TX-249893122", "status": "SUCCESS", "method": "card", "creationDate": "2022-01-23T10:50:54.261Z", "amount": 11334, "currency": "EGP", "card": { "cardInfo": { "cardHolderName": "John Doe", "cardBrand": "Mastercard", "maskedCard": "511111******1118" }, "merchant": { "merchantRedirectURL": "http://localhost:9000/callback" }, "amount": 11334, "currency": "EGP" }, "metaData": { "time": "2022-01-23T10:50:52.562Z" }, "transactionResponseCode": "00", "transactionResponseMessage": { "en": "Approved", "ar": "تمت الموافقة" }, "channel": "online | e-commerce", "merchantDetails": { "businessEmail": "billing@example.com" }, "signatureKeys": [ "amount", "channel", "currency", "kashierOrderId", "merchantOrderId", "method", "orderReference", "status", "transactionId", "transactionResponseCode" ], "platform": {} } } ``` ## Field notes [#field-notes] * `merchantOrderId` — pass this key back to Kashier to reconcile your orders. Learn more about [order reconciliation](/docs/accept-payments/order-reconciliation). * `settlementInfo` — per-transaction fee and settlement reconciliation block: `vat`, `sellingRate`, `sellingFlat`, `totalSellingRate`, `totalSellingFees`, `settledAmount`. * `posSerialNumber`, `posTerminalId`, `posBranchName` — identify the physical POS device, terminal, and branch on POS-originated transactions. Present only when the transaction came through a POS channel. * `installmentPlan` — installment plan details when the transaction was paid via an installment plan (e.g. valU, bank installments). * `card` — present only when `method` is `card`. Carries the masked `cardInfo` (`cardHolderName`, `cardBrand`, `maskedCard`) and `merchant.merchantRedirectURL`. This is where masked card details arrive. * `sourceOfFunds` — source-of-funds detail. Older published samples showed full card tokens here (`cardHash`, `ccvToken`, `cardDataToken`) alongside a `3DSecure.processACSRedirectURL` block; the current payload builder emits neither, so don't build against them. Read masked card details from `card.cardInfo` instead. * `merchantDetails` — merchant identification. The payload builder sets `businessEmail`; treat any other key as unconfirmed until you see it in a delivery of your own. * `apikeyname` — the name of the API key the transaction was created with, useful when you run several keys across stores or environments. * `paymentMethod` and `channel` — the method object and the channel the payment came through (e.g. `online`, `e-commerce`). * `originDetails` — origin detail for the transaction. * `platform` — the originating platform. Empty for direct API integrations, populated for platform-originated orders such as ecommerce plugins, so don't assume `{}`. * `signatureKeys` — the keys of the `data` object that Kashier used to compute the HMAC signature for this request. Sort the array alphabetically, select those keys and their values from `data`, and build the signature payload from them. Verify the result against the `x-kashier-signature` header — see [signature verification](/docs/webhooks#step-4-verify-the-signature). * `hash` — an internal Kashier integrity field (sent for non-Wix platforms), not a merchant-facing signature. It's signed with a server-side secret you don't hold, so **do not attempt to verify it**. Always verify webhooks using `x-kashier-signature` as described above; ignore `data.hash` if present. # CS-Cart (/docs/plugins/cs-cart) CS-Cart logo Accept Kashier payments on your CS-Cart store — debit card, credit card, bank installments (with 3D Secure), and all supported wallets. Customers pay on your site; the plugin works across all browsers and is compatible with the latest version of CS-Cart. ## Prerequisites [#prerequisites] * Log in to the [Kashier platform](https://merchant.kashier.io/login) * Navigate to the "Integrate now" page * Click on "Generate" for the customizable form service ## Integration [#integration] The CS-Cart Kashier plugin is developed using [payment sessions](/docs/accept-payments/payment-sessions). Features: 1. **Embedded checkout.** Customers pay on your site via a Payment Session, no redirect. 2. Supports multiple payment methods: Card, Bank Installment, and Wallet. Download the plugin and follow the installation steps from our [GitHub](https://github.com/Kashier-payments/kashier-cs-cart-plugin). # E-commerce plugins (/docs/plugins) Add Kashier to your e-commerce store with a ready-made plugin — no custom integration. Every plugin accepts debit and credit cards, bank installments (with 3D Secure), and all supported wallets. ## Available integrations [#available-integrations] 1. [WooCommerce Kashier Payment Gateway plugin](/docs/plugins/woocommerce) 2. [PrestaShop](/docs/plugins/prestashop) 3. [Magento 2.3](/docs/plugins/magento) 4. [OpenCart](/docs/plugins/opencart) 5. [Shopify plugin](/docs/plugins/shopify) 6. [CS-Cart](/docs/plugins/cs-cart) 7. [Odoo 15](/docs/plugins/odoo) 8. [VikBooking and VikAppointments](/docs/plugins/vik-booking) 9. [WHMCS](/docs/plugins/whmcs) 10. [Wix](/docs/plugins/wix) 11. [Booking BA](https://ba-booking.com/shop/downloads/babe-payment-kashier) # Magento (/docs/plugins/magento) Magento logo Accept Kashier payments on your Magento store — debit card, credit card, bank installments (with 3D Secure), and all supported wallets. Customers pay on your site without being redirected; the extension works across all browsers and is compatible with the latest versions of Magento. ## Prerequisites [#prerequisites] * Log in to the [Kashier platform](https://merchant.kashier.io/login) * Navigate to the "Integrate now" page * Click on "Generate" for the customizable form service ## Integration [#integration] The Magento Kashier plugin is developed using [payment sessions](/docs/accept-payments/payment-sessions). Features: 1. **Embedded checkout.** Customers pay on your site via a Payment Session, no redirect. 2. Supports multiple payment methods: Card, Bank Installment, and Wallet. Download the plugin and follow the installation steps from our [GitHub](https://github.com/Kashier-payments/Kashier_Magento_2.3x_Plugin). # Odoo (/docs/plugins/odoo) Odoo logo Accept Kashier payments on your Odoo eCommerce store — debit card, credit card, bank installments (with 3D Secure), and all supported wallets. Customers pay on your site through an embedded Payment Session, with no redirects. The add-on is published per Odoo version — install the branch that matches your server: | Odoo version | Add-on | | ------------ | ---------------------------------------------------------------------------------------- | | Odoo 16 | [`v16` branch](https://github.com/Kashier-payments/Kashier-Odoo-Payment-Add-on/tree/v16) | | Odoo 15 | [`v15` branch](https://github.com/Kashier-payments/Kashier-Odoo-Payment-Add-on/tree/v15) | Running a newer Odoo? [Contact support](/docs/resources/support) for current availability. ## Prerequisites [#prerequisites] * Log in to the [Kashier platform](https://merchant.kashier.io/login) * Navigate to **Integrate now → Payment API keys** and copy (or generate) a test API key * Copy your merchant ID (`MID-...`), shown under your username * Enter both in the add-on's configuration page (**Invoicing → Configuration → Payments → Payment Acquirers**) ## Integration [#integration] The Odoo Kashier plugin is developed using [payment sessions](/docs/accept-payments/payment-sessions). Features: 1. **Embedded checkout.** Customers pay on your site via a Payment Session, no redirect. 2. No redirects. 3. Supports multiple payment methods: Card, Bank Installments, and Wallet. Download the plugin and follow the installation steps from our [GitHub](https://github.com/Kashier-payments/Kashier-Odoo-Payment-Add-on). # OpenCart (/docs/plugins/opencart) OpenCart logo Accept Kashier payments on your OpenCart store — debit card, credit card, bank installments (with 3D Secure), and all supported wallets. Customers pay on your site without being redirected; the extension works across all browsers and is compatible with the latest version of OpenCart. ## Prerequisites [#prerequisites] * Log in to the [Kashier platform](https://merchant.kashier.io/login) * Navigate to the "Integrate now" page * Click on "Generate" for the customizable form service ## Integration [#integration] The OpenCart Kashier plugin is developed using [payment sessions](/docs/accept-payments/payment-sessions). Features: 1. **Embedded checkout.** Customers pay on your site via a Payment Session, no redirect. 2. Supports multiple payment methods: Card, Bank Installment, and Wallet. Download the plugin and follow the installation steps from our [GitHub](https://github.com/Kashier-payments/kashier-openCart-plugin). # PrestaShop (/docs/plugins/prestashop) PrestaShop logo Accept Kashier payments on your PrestaShop store — debit card, credit card, bank installments (with 3D Secure), and all supported wallets. Customers pay on your site without being redirected; the module works across all browsers and is compatible with the latest version of PrestaShop. Supported PrestaShop versions: * [PrestaShop 1.7.x](https://github.com/Kashier-payments/kashier-prestashop-1.7) * [PrestaShop 1.6.x](https://github.com/Kashier-payments/kashier-prestashop-1.6) ## Prerequisites [#prerequisites] * Log in to the [Kashier platform](https://merchant.kashier.io/login) * Navigate to the "Integrate now" page * Click on "Generate" for the customizable form service ## Integration [#integration] The PrestaShop Kashier plugin is developed using [payment sessions](/docs/accept-payments/payment-sessions). Features: 1. **Embedded checkout.** Customers pay on your site via a Payment Session, no redirect. 2. Supports multiple payment methods: Card, Bank Installment, and Wallet. Download the plugin and follow the installation steps from our [GitHub](https://github.com/Kashier-payments/kashier-prestashop-1.7): * [PrestaShop 1.7.x](https://github.com/Kashier-payments/kashier-prestashop-1.7) * [PrestaShop 1.6.x](https://github.com/Kashier-payments/kashier-prestashop-1.6) # Shopify (/docs/plugins/shopify) Shopify logo Accept Kashier payments on your Shopify store by installing the Kashier Online Payments app from the Shopify marketplace. ## Features [#features] * Fully PCI DSS compliant as a Level 1 Service for merchants operating in Egypt. * **Embedded checkout.** Customers pay on your site via a [Payment Session](/docs/accept-payments/payment-sessions), no redirect. * 3D Secure card authentication support. * Supports multiple payment methods: 1. Card payments 2. Wallet payments 3. Bank installments payment * Supports acquiring multiple currencies: "EGP, USD, GBP, EUR". * Plug and play. ## Prerequisites [#prerequisites] * Account on the [Kashier platform](https://merchant.kashier.io/login) * A store on [Shopify](https://www.shopify.com/). * In case of test mode, make sure your email is verified. Test mode is available on the Shopify integration. ## Integration steps [#integration-steps] * Navigate to the [Kashier Online Payment app](https://apps.shopify.com/kashier-online-payments) in the Shopify marketplace. * Make sure you are logged in to your Shopify store. * Click the "Add App" button to start the installation to your Shopify website. ## Installation steps [#installation-steps] * Go to the Kashier app page on Shopify. Shopify step 1 * Click on the "Install" button as shown below. Shopify step 2 * This will redirect you to the Kashier app configuration page to fill in some configuration keys as shown below. Learn more about obtaining your [merchant ID and keys](/docs/get-started/api-keys). Shopify step 3 * After filling in the required configuration keys, click the "Return to shopify" button. Shopify step 4 * This will redirect you to the Kashier settings page in your Shopify store. * Click the "Activate" button as shown below. Shopify step 5 You have now finished installing the latest Kashier Shopify plugin and can start accepting payments. # VikBooking and VikAppointments (/docs/plugins/vik-booking) Vikwp logo Accept Kashier payments on your WordPress [Vikwp](https://vikwp.com) site ([VikBooking](https://vikwp.com/plugin/vikbooking), [VikAppointments](https://vikwp.com/plugin/vikappointments)) — debit card, credit card, bank installments (with 3D Secure), and all supported wallets. Customers pay on your site without being redirected; the module works across all browsers and is compatible with the latest version of Vikwp. ## Prerequisites [#prerequisites] * Log in to the [Kashier platform](https://merchant.kashier.io/login) * Navigate to the "Integrate now" page * Click on "Generate" for the customizable form service ## Integration [#integration] The Vikwp Kashier plugin is developed using [payment sessions](/docs/accept-payments/payment-sessions). Features: 1. **Embedded checkout.** Customers pay on your site via a Payment Session, no redirect. 2. Supports multiple payment methods: Card, Bank Installments, and Wallet. Download the plugin and follow the installation steps from our [GitHub](https://github.com/Kashier-payments/Kashier-Vikwp-UI-Plugin). # WHMCS (/docs/plugins/whmcs) WHMCS logo Accept Kashier payments in WHMCS — debit card, credit card, bank installments (with 3D Secure), and all supported wallets. Customers pay on your site; the extension works across all browsers and is compatible with the latest versions of WHMCS. ## Prerequisites [#prerequisites] * Log in to the [Kashier platform](https://merchant.kashier.io/login) * Navigate to the "Integrate now" page * Click on "Generate" for the customizable form service ## Integration [#integration] The WHMCS Kashier plugin is developed using [payment sessions](/docs/accept-payments/payment-sessions). Features: 1. **Embedded checkout.** Customers pay on your site via a Payment Session, no redirect. 2. Supports multiple payment methods: Card, Bank Installments, and Wallet. Download the plugin and follow the installation steps from our [GitHub](https://github.com/Kashier-payments/Kashier-Whmcs-Plugin). # Wix (/docs/plugins/wix) Accept Kashier payments on your Wix site by connecting Kashier from the Wix dashboard. The connection uses the Kashier hosted payment page, so there is nothing to install and no code to write. ## Features [#features] * Hosted payment page (HPP) integration. * Supports multiple payment methods: 1. Card payments 2. Wallet payments 3. Bank installments payment * Supports acquiring multiple currencies: "EGP, USD, GBP, EUR". ## Prerequisites [#prerequisites] * A live account on the [Kashier platform](https://merchant.kashier.io/login). * A Premium Wix site. Test mode is not available on the Wix integration. ## Connect Kashier [#connect-kashier] In your Wix dashboard: 1. Go to Settings → Accept Payments → See More Payment Options. 2. Select Kashier, then click Connect. 3. Enter your merchant ID and API key. Learn more about obtaining your [merchant ID and keys](/docs/get-started/api-keys). ## Reason codes on Wix [#reason-codes-on-wix] Kashier translates its own `transactionResponseCode` into a separate, Wix-specific reason code before notifying Wix. A transaction record on the Wix side shows the Wix code, not the Kashier code documented in [payment reason codes](/docs/accept-payments/payment-reason-codes). | Kashier code | Wix code | Meaning | | ------------------------------------ | ------------------------ | -------------------------------------------------------------------------------------- | | `00` | `0` | Approved | | `14` | `3015` | Invalid card information | | `33`, `54` | `3013` | Expired card | | `36` | `5002` | Restricted card | | `51` | `3028` | Insufficient funds | | `55` | `3036` | Wrong PIN | | `61`, `65` | `3019` | Limit exceeded | | `97` | `3016` | CVV mismatch | | `05` | `3016`, `3011` or `3000` | Do not honor. `3016` when CVV did not match, `3011` when it matched, `3000` otherwise. | | `N` | `3016` | 3D Secure authentication outcome | | `P`, `U` | `3004` | 3D Secure authentication outcome | | `M`, `S`, `X` | `3011` | 3D Secure authentication outcome | | `AUTHENTICATION_FAILED`, `ACS_ERROR` | `3004` | 3D Secure failure | | `CARD_NOT_ENROLLED` | `3005` | Card not enrolled in 3D Secure | | Anything unmapped | `3000` | Fallback | ## Refund amounts are in minor units [#refund-amounts-are-in-minor-units] Refund notifications sent to Wix carry `event.refund.amount` in **minor units** — the refund amount multiplied by 100. A 250.00 EGP refund is sent as `25000`. This differs from the standard Kashier webhook, where `data.amount` is the plain order amount. If a refund on your Wix side looks 100 times too large, check that you are reading the value as minor units. # WooCommerce (/docs/plugins/woocommerce) WooCommerce logo Accept Kashier payments on your WooCommerce store — cards, wallets, bank installments (with 3D Secure), and BNPL. There are three Kashier plugins for WooCommerce; pick the one that matches your checkout setup. | Plugin | Use it when | Repository | | -------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | Classic checkout | Your store uses the standard WooCommerce checkout flow | [Kashier-WooCommerce-UI-Plugin](https://github.com/Kashier-payments/Kashier-WooCommerce-UI-Plugin) | | Block-based checkout | Your store uses the WooCommerce block editor checkout | [Kashier-Block-Based-Woocommerce](https://github.com/Kashier-payments/Kashier-Block-Based-Woocommerce) | | Embedded form | You need the form embedded in your page, connected accounts, or WooCommerce Subscriptions | Contact [support](/docs/resources/support) | ## Prerequisites [#prerequisites] All plugins need your Kashier credentials: * Log in to the [Kashier dashboard](https://merchant.kashier.io/login) * Navigate to the **Integration** page * Copy your API key and secret key; you enter them in the plugin settings after installation. ## Classic checkout [#classic-checkout] Embedded checkout for the standard WooCommerce checkout flow, built on [payment sessions](/docs/accept-payments/payment-sessions). * Payment methods: card, wallet, bank installments, valU, Souhoola, Aman * Apple Pay enabled on the live environment * Currencies: EGP, USD, GBP, EUR * Requires WooCommerce 2.6+ (plugin v4.0.0) ### Install it [#install-it] 1. Download [Kashier-WooCommerce-Plugin-master.zip](https://raw.githubusercontent.com/Kashier-payments/Kashier-WooCommerce-UI-Plugin/main/Kashier-WooCommerce-Plugin-master.zip). 2. On WooCommerce 8 or above, first enable HPOS: **WooCommerce → Settings → Advanced → Features**. 3. In WordPress, go to **Plugins → Add New → Upload Plugin**, upload the ZIP, and activate it. 4. Go to **WooCommerce → Settings → Payments**. The plugin adds three Kashier payment methods; enable the ones you want to offer. 5. Open each enabled method's configuration and enter your **Merchant ID**, **API key**, and **Secret key** from the [dashboard](https://merchant.kashier.io/en/dashboard/integration). 6. Tick **Enable**, leave **test mode** on while you are testing, set the title and description customers will see, and save. 7. Place an order on your store and pay with a [test card](/docs/get-started/testing) to confirm the flow end to end. To go live, swap in your live API key and secret key and untick test mode — see [going live](/docs/get-started/going-live). The plugin has an **Enforce EGP Payment** setting. Turn it on to settle every Kashier payment in EGP regardless of the currency your storefront displays; you then supply the exchange rate from your display currency to EGP, which must be greater than 1. Screenshots for each step are in the [Kashier-WooCommerce-UI-Plugin repository](https://github.com/Kashier-payments/Kashier-WooCommerce-UI-Plugin). ## Block-based checkout [#block-based-checkout] Embedded checkout for the WooCommerce block editor checkout experience, built on [payment sessions](/docs/accept-payments/payment-sessions). * Payment methods: card, wallet, bank installments, valU, Souhoola, Aman, Mogo, Tru * Apple Pay enabled on the live environment * Currencies: EGP, USD, GBP, EUR * Requires WooCommerce 8.0+ and WordPress 6.0+ (plugin v2.1.4) * On WooCommerce 8+, enable the HPOS feature first: **WooCommerce → Settings → Advanced → Features** (the repository shows the exact screen) ### Install it [#install-it-1] The steps match the classic plugin above, with a different package: 1. Download [Kashier-Block-Based-WooCommerce-Plugin.zip](https://raw.githubusercontent.com/Kashier-payments/Kashier-Block-Based-Woocommerce/main/Kashier-Block-Based-WooCommerce-Plugin.zip). 2. Enable HPOS: **WooCommerce → Settings → Advanced → Features**. 3. **Plugins → Add New → Upload Plugin**, upload the ZIP, activate. 4. **WooCommerce → Settings → Payments** — enable the Kashier methods you want. 5. Enter your Merchant ID, API key, and Secret key from the [dashboard](https://merchant.kashier.io/en/dashboard/integration), keep test mode on, and save. 6. Place a test order to confirm the flow. Screenshots for each step are in the [Kashier-Block-Based-Woocommerce repository](https://github.com/Kashier-payments/Kashier-Block-Based-Woocommerce). ## Embedded form [#embedded-form] A customized embedded form integration: * Embedded form on your own checkout page * [Connected accounts](/docs/accept-payments/connected-accounts) * [WooCommerce Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions) payments and tokenization The embedded form plugin is distributed by the Kashier team — [contact support](/docs/resources/support) to get the package and installation guide. # List payment sessions (/docs/api-reference/payment-sessions/listPaymentSessions) Lists the payment sessions on your merchant account — the sessions you created, in whatever state they reached. Use it to sweep up sessions your own records lost track of; to poll one particular se… # Create payment session (/docs/api-reference/payment-sessions/createPaymentSession) Creates a payment session. The response contains a sessionUrl that is used to redirect the customer to the payment page — you can use it as the src attribute in a link or iframe. # Get payment session by id (/docs/api-reference/payment-sessions/getPaymentSessionById) Returns the session document for one session — its current status, the payment parameters it was created with, and its history of state changes. This endpoint takes no credential. It is the read th… # Abandon a payment session (/docs/api-reference/payment-sessions/abandonPaymentSession) Marks a session as ABANDONED — the customer closed the checkout, or you decided the session is no longer wanted. Abandoning is the clean way to end a session you will not use, rather than leaving i… # Get payment session (/docs/api-reference/payment-sessions/getPaymentSession) Retrieves the current state (and history) of a payment session. # Get all transactions (/docs/api-reference/transactions/listTransactions) Retrieves a list of transaction records based on multiple optional filter parameters, such as currency, amount, status, channel, etc. The data can be paginated, sorted, and searched. # Get transaction details (/docs/api-reference/transactions/getTransactionDetails) Retrieves detailed information about a single transaction using its unique reference ID. # Look up an order and its transactions (/docs/api-reference/transactions/searchOrders) The endpoint to call after a payment when you need to know what actually happened. It returns the whole order — every transaction attempted against it, each one's response code, and Kashier's own r… # Export transaction batches (/docs/api-reference/transactions/exportTransactionBatches) Exports transaction data based on specific filters such as status and date range. It's typically used for reporting, reconciliation, and data analysis by aggregators or merchants. The export is del… # Refund, void, or capture an order (/docs/api-reference/order-operations/updateOrder) Performs an operation against an existing order, selected by the apiOperation field in the body: - REFUND — repay your customer in part or fully. For partial refunds pass an amount in transaction.… # Refund an order (/docs/api-reference/order-operations/refundOrder) Repays your customer, in part or in full, from your available Kashier balance. This is the same operation as PUT /v3/orders/{orderId} with apiOperation: REFUND, one hop closer. The checkout host (t… # Void an order (/docs/api-reference/order-operations/voidOrder) Cancels a transaction on the order before it settles — the clean undo while the money has not actually moved yet. Use it to cancel a pay, an authorization, a capture or a refund; pass the targetTra… # Capture an authorized order (/docs/api-reference/order-operations/captureOrder) Takes the money on an order you previously authorized, fully or partially. Capture before the hold expires — 7 or 30 days depending on your configuration — or the authorization lapses and the funds… # Reverse an order (/docs/api-reference/order-operations/reverseOrder) Reverses a transaction at the rail level. This is the card-present and BNPL undo — for an ordinary online card sale reach for void (before settlement) or refund (after) instead, which is almost alw… # Pay with token (/docs/api-reference/tokens/payWithToken) Creates a new payment request using a saved card token. (The docs show the URL with a trailing slash: /v3/orders/.) The request is authenticated with a Kashier-Hash header (order hash generated wit… # Retrieve tokens (/docs/api-reference/tokens/retrieveTokens) Retrieves a customer's saved card tokens and info. The request is validated with a Kashier-Hash header — an HMAC SHA256 of the path /?tokenization={mid}.{customerReference} keyed with your Payment… # Get token (/docs/api-reference/tokens/getToken) Retrieves a single saved card token by its value, returning the masked card number, expiry, and cardholder name. Use it to show a customer which card is on file before charging it, or to check that… # Delete token (/docs/api-reference/tokens/deleteToken) Removes a saved card token — useful for removing an expired card, a new card, or a stolen card's old information. The request is validated with a Kashier-Hash header — an HMAC SHA256 hash of the pa… # List saved card tokens (/docs/api-reference/tokens/listSavedTokens) Lists every card token saved on your merchant account, with the agreement each one was saved under. This is the account-wide view, and it is a different endpoint from the customer-scoped GET /v3/ca… # Export saved card tokens (/docs/api-reference/tokens/exportSavedTokens) Returns the saved-token list as a downloadable file rather than JSON — the same data as List saved card tokens, in a form you can hand to finance or diff against your own records. Because the respo… # Delete a saved card token (/docs/api-reference/tokens/deleteSavedToken) Removes a saved card token. Any recurring charge that still references it fails afterwards, so cancel the billing arrangement on your side first. Deleting the token is also how you honour a custome… # Create token (/docs/api-reference/tokens/createToken) Saves a card and returns a reusable cardToken you can charge later with Pay with token. Send the raw card details once; Kashier stores them and hands back only the token and a masked card number, s… # Get available banks (/docs/api-reference/installments/getInstallmentBanks) Retrieves the installment-enabled banks (with their plans, BIN ranges, and terms & conditions) for a given merchant and product price. Step 1 of retrieving installment plans. # Get plans for specific bank (/docs/api-reference/installments/getInstallmentPlans) Retrieves the installment plans for a specific bank (selected by fiId, the banknSystemID retrieved from the "Get available banks" step). After a successful response, the monthly amount for every pl… # Get account info (/docs/api-reference/payouts/getAccountInfo) Retrieves account info — balances, payout method, and last-transfer details for the merchant identified by the secret key. # List all transfers (/docs/api-reference/payouts/listTransfers) Lists all payout transfers, with pagination. # Get transfer details (/docs/api-reference/payouts/getTransferDetails) Retrieves the details of a payout transfer, including its status history and fees. # Fees inquiry (/docs/api-reference/payouts/feeInquiry) Inquires about the fees for one or more prospective transfers. # Create transfer (/docs/api-reference/payouts/createTransfer) Creates a single payout transfer. # Bulk transfers (/docs/api-reference/payouts/createBulkTransfer) Creates a bulk payout transfer from an uploaded file of transfers. # List payout schedulers (/docs/api-reference/payouts/listPayoutSchedulers) Lists your recurring payout schedulers, with pagination. A scheduler is a saved transfer template that Kashier re-runs on the frequency you set, so you can pay a recurring salary or a monthly suppl… # Get payout scheduler details (/docs/api-reference/payouts/getPayoutScheduler) Retrieves one recurring payout scheduler, including the transfer template it repeats, its recurring frequency, and the date it next runs. # Create payout scheduler (/docs/api-reference/payouts/createPayoutScheduler) Creates a recurring payout scheduler from a single transfer template. Kashier repeats the transfer on the recurringFrequency you set, starting on startDate, until you deactivate the scheduler or it… # List payout batches (/docs/api-reference/payouts/listTransferBatches) Lists your payout batches, with pagination. Every bulk payout you submit with Bulk transfers becomes a batch whose row shows how many of its transfers have succeeded and how many have failed, so yo… # Get payout batch details (/docs/api-reference/payouts/getTransferBatch) Retrieves one payout batch together with the transfers it contains, paginated. Use it to find which rows of a bulk payout failed and why. # Get transfer by merchant transfer id (/docs/api-reference/payouts/getTransferByMerchantTransferId) Retrieves a payout transfer using the merchantTransferId your own systems assigned to it, so you can reconcile against your records without having to store Kashier's transferId. merchantTransferId… # List all customers (/docs/api-reference/customers/listCustomers) Retrieves the merchant's customers. All parameters are optional and can be combined; results are paginated. # Add a new customer (/docs/api-reference/customers/createCustomer) Creates a customer profile. Kashier assigns the customerId (a C- prefixed value) that you use to link the customer to payments and payment links. # Get customer details (/docs/api-reference/customers/getCustomer) Retrieves a single customer, including the timeLine of changes made to the profile and who made them. # Delete a customer (/docs/api-reference/customers/deleteCustomer) Deletes a customer profile. # Update a customer (/docs/api-reference/customers/updateCustomer) Updates a customer profile. Send the fields you want the customer to end up with — customFields replaces the existing array rather than merging into it. # Get a customer's payment links (/docs/api-reference/customers/getCustomerPaymentLinks) Retrieves the payment links created for a single customer — the customer-side view of the Payment links endpoints. Results are paginated. # Get a customer's audit logs (/docs/api-reference/customers/getCustomerAuditLogs) Retrieves the full audit trail for a customer — every create and edit, which fields changed, and which dashboard user made the change. This is the same data the profile returns under timeLine, retr… # Export customers (/docs/api-reference/customers/exportCustomers) Exports the customers matching the given filters. The export is generated asynchronously and delivered to the merchant account's email address, so the response only acknowledges that the request is… # Export customers with their payment links (/docs/api-reference/customers/exportCustomerPaymentLinks) Exports a chosen set of customers together with the payment links created for them. Pass customerIds to export specific customers, or the date/search filters to export everything that matches. Like… # Upload a customers sheet for review (/docs/api-reference/customers/importCustomers) Step 1 of the bulk import. Uploads an Excel sheet of customers and validates it without saving anything. Rows that fail validation come back with their errors populated so you can correct them; if… # Save the uploaded customers (/docs/api-reference/customers/saveImportedCustomers) Step 2 of the bulk import. Persists the customers that were validated by importCustomers, identified by the correlationId returned there. Separating validation from the write keeps a partially inva… # Create invoice (/docs/api-reference/invoices/createInvoice) Creates an invoice (payment request). Kashier mints the invoice identifier for you — PR-… for a simple request, INV-… for a professional invoice, and ORD-… for an order — and returns a payment link… # List invoices (/docs/api-reference/invoices/listInvoices) Lists your invoices, with pagination. Combine the filters to narrow the list down — for example paymentStatus=unpaid together with a date range to find everything still outstanding this month. # Get invoice (/docs/api-reference/invoices/getInvoice) Retrieves a single invoice, including its line items and any payment transactions recorded against it. # Delete invoice (/docs/api-reference/invoices/deleteInvoice) Deletes an invoice. The invoice is soft-deleted — it stops being payable and disappears from List invoices, but its history is retained. # Update invoice (/docs/api-reference/invoices/updateInvoice) Updates an invoice, or cancels it. Send operation: cancel to cancel an unpaid invoice; send any of the editable fields to change it. Only unpaid invoices can be edited. # Share invoice (/docs/api-reference/invoices/shareInvoice) Sends the invoice's payment link to a customer by email or SMS. Kashier records the delivery outcome on the invoice, so a later Get invoice shows whether the message was delivered, opened, or click… # Export invoices (/docs/api-reference/invoices/exportInvoices) Exports the invoices matching your filters as a spreadsheet, returned inline in the response. For large exports use Export invoices asynchronously instead, which emails you the file once it is ready. # Export invoices asynchronously (/docs/api-reference/invoices/exportInvoicesAsync) Starts an invoice export in the background and emails the finished file to the account that made the request. Use this instead of Export invoices when the date range is wide enough that a synchrono… # Import invoices (/docs/api-reference/invoices/importInvoices) Creates many invoices at once from an uploaded spreadsheet. Each row becomes an invoice; the response reports how many rows were accepted and which ones failed, so you can fix and re-upload just th… # Import invoices (/docs/api-reference/payment-links/importInvoices) Creates many invoices at once from an uploaded spreadsheet. Each row becomes an invoice; the response reports how many rows were accepted and which ones failed, so you can fix and re-upload just th… # Get all payment links (/docs/api-reference/payment-links/listPaymentLinks) Retrieves the merchant's payment links. All filters are optional and can be combined; results are paginated. If the currency conversion feature is enabled on your account you can also filter by a v… # Create a payment link (/docs/api-reference/payment-links/createPaymentLink) Creates a single payment link for one customer. Set currency to a virtual currency code (USDVIRTUAL, EURVIRTUAL, GBPVIRTUAL, SARVIRTUAL, AEDVIRTUAL) with totalAmount expressed in that currency to c… # Get payment link statistics (/docs/api-reference/payment-links/getPaymentLinkStatistics) Returns aggregate statistics across the merchant's payment links (counts and amounts broken down by payment status). Accepts the same optional filters as listPaymentLinks, so the totals match the l… # Get payment link details (/docs/api-reference/payment-links/getPaymentLink) Retrieves a single payment link, including its extra fees, invoice items, and the full audit history of the link (creation, edits, shares, and payments). # Delete a payment link (/docs/api-reference/payment-links/deletePaymentLink) Deletes a payment link. The link is soft-deleted (isDeleted becomes true) and stops being payable. # Update a payment link (/docs/api-reference/payment-links/updatePaymentLink) Updates an existing payment link. The editable fields are wrapped in a paymentLink object; send the full object you want the link to end up with. # Get payment link events (/docs/api-reference/payment-links/getPaymentLinkEvents) Retrieves the event/audit trail of a single payment link — the same records that getPaymentLink returns under history.records, retrievable on their own so you can page through a long trail without… # Share a payment link (/docs/api-reference/payment-links/sharePaymentLink) Sends an existing payment link to a customer by email or SMS. Use operation to pick the channel and key to carry the matching destination. # Re-share payment links (/docs/api-reference/payment-links/resharePaymentLinks) Re-sends one or more existing payment links to the customers they were originally shared with, on the channel used the first time. Useful for chasing unpaid links in bulk without re-entering each c… # Create and share a payment link (/docs/api-reference/payment-links/createAndSharePaymentLink) Creates a payment link and immediately sends it to the customer, in a single call. The body is the same as createPaymentLink; the customer's email address or phone number determines the channel the… # Export payment links (/docs/api-reference/payment-links/exportPaymentLinks) Exports the payment links matching the given filters. The export is generated asynchronously and delivered to the merchant account's email address, so the response only acknowledges that the reques… # Save the uploaded payment links (/docs/api-reference/payment-links/saveImportedPaymentLinks) Step 2 of the bulk upload. Persists the payment links that were validated by importPaymentLinks, identified by the correlationId returned there. Separating validation from the write keeps a partial… # List payment pages (/docs/api-reference/payment-pages/listPaymentPages) Lists your payment pages, with pagination, filtering, and search. # Create payment page (/docs/api-reference/payment-pages/createPaymentPage) Creates a payment page. Kashier mints a link identifier (PP-…) for the page, which is what the public URL and the /public reads are keyed on. Set paymentObject.isFixed to collect one fixed amount,… # Get payment page (/docs/api-reference/payment-pages/getPaymentPage) Retrieves a single payment page, including its products, extra fields, and fees. # Delete payment page (/docs/api-reference/payment-pages/deletePaymentPage) Deletes a payment page. The page is soft-deleted — the public link stops working and the page drops out of List payment pages, but the payments already collected against it are retained. # Update payment page (/docs/api-reference/payment-pages/updatePaymentPage) Updates a payment page. Send only the fields you want to change; anything you omit keeps its current value. Unpublishing a page (isPublished: false) takes it offline without deleting it. # List payments for a payment page (/docs/api-reference/payment-pages/listPaymentPagePayments) Lists the payments collected against one payment page, with pagination. Each record carries the customer details captured on the page and the underlying card, wallet, or cash-on-delivery transactions. # Get public payment page (/docs/api-reference/payment-pages/getPublicPaymentPage) Retrieves the customer-facing view of a published payment page. No authentication — this is the read the hosted page itself performs to render your catalogue, so it returns only what is safe to sho… # Share payment page (/docs/api-reference/payment-pages/sharePaymentPage) Sends a payment page's link to one or more recipients by email or SMS. Pass every recipient in key to send a single batch. # Get a payment page payment (/docs/api-reference/payment-pages/getPaymentPagePayment) Retrieves one payment collected through a payment page, looked up by the order id the page assigned to it. # Get a payment page payment (public) (/docs/api-reference/payment-pages/getPublicPaymentPagePayment) The customer-facing view of one payment page payment. No authentication — this is the read the hosted page performs on its own confirmation screen. Returns 404 when the order does not exist. # Export payment pages (/docs/api-reference/payment-pages/exportPaymentPages) Exports your payment pages as a spreadsheet, returned inline in the response. For a wide date range use Export payment pages asynchronously instead. # Export payment pages asynchronously (/docs/api-reference/payment-pages/exportPaymentPagesAsync) Starts a payment pages export in the background and emails the finished file to the account that made the request. # Export payments for a payment page (/docs/api-reference/payment-pages/exportPaymentPagePayments) Exports the payments collected against one payment page as a spreadsheet, returned inline in the response. # Export payments for a payment page asynchronously (/docs/api-reference/payment-pages/exportPaymentPagePaymentsAsync) Starts an export of one page's payments in the background and emails the finished file to the account that made the request. # List balance accounts (/docs/api-reference/balance-and-accounts/listBalanceAccountsOverview) Lists the balance accounts belonging to the merchant identified by the secret key, with the current balance of each. This is the entry point for the balance overview — take an accountId from here a… # Get balance account overview (/docs/api-reference/balance-and-accounts/getBalanceAccountOverview) Retrieves the balance overview of a single account — the headline figures (total balance, available balance) you would show on a dashboard. Use GET /v2/account/{accountId} instead when you need the… # Get payments overview for an account (/docs/api-reference/balance-and-accounts/getBalancePaymentsOverview) Summarises the money that came into a balance account over a date range — the settled payments credited to it. Pair it with the payouts overview to see both sides of the account for the same period. # Get payouts overview for an account (/docs/api-reference/balance-and-accounts/getBalancePayoutsOverview) Summarises the money that left a balance account over a date range — the payouts debited from it. Pair it with the payments overview to see both sides of the account for the same period. # Get balance account details (/docs/api-reference/balance-and-accounts/getBalanceAccountDetails) Retrieves one balance account in full — balances, the payout method money leaves by, and the last transfer made from it. GET /v2/account returns the same document for every account you own; use thi… # List balance records for an account (/docs/api-reference/balance-and-accounts/listBalanceAccountRecords) Lists the balance records of one account, newest first — the statement lines behind the balance. Each record is a single movement (a settlement credit, a payout debit, a refund, or an adjustment) a… # Export balance records for an account (/docs/api-reference/balance-and-accounts/exportBalanceAccountRecords) Exports one account's balance records as a spreadsheet, applying the same filters as the records list. Pass email to have the export delivered to that address instead of returned in the response. # Export balance accounts (/docs/api-reference/balance-and-accounts/exportBalanceAccounts) Exports the merchant's balance accounts and their balances as a spreadsheet. Pass email to have the export delivered to that address instead of returned in the response. # Search balance accounts by name (/docs/api-reference/balance-and-accounts/searchBalanceAccounts) Looks up your balance accounts by name and returns just the id and name of each match — a lightweight read for populating an account picker before calling one of the detail reads. # Get the primary account's payout method (/docs/api-reference/balance-and-accounts/getPrimaryAccountPayoutMethod) Retrieves the payout method configured on your primary balance account — the bank account or wallet that settled money is paid out to. The fields inside payoutFields depend on method: a bank accoun… # Get balance record details (/docs/api-reference/balance-and-accounts/getBalanceRecordDetails) Retrieves one balance record in full — the amount, whether it credited or debited the account, the value date the money counted from, and the origin/originReference pointing back at whatever produc… # Get payout details for a balance record (/docs/api-reference/balance-and-accounts/getBalancePayoutDetails) Expands a payout balance record into the individual transactions it paid out. Use it to answer "which sales made up this payout?" — the record gives you the total that left the account, and this re… # Export payout details for a balance record (/docs/api-reference/balance-and-accounts/exportBalancePayoutDetails) Exports the transactions behind a payout balance record as a spreadsheet, applying the same status and date filters as the payout details read. # List holds on a balance account (/docs/api-reference/balance-and-accounts/listBalanceAccountHolds) Lists the holds placed on a balance account. A hold reserves part of the balance so it cannot be paid out yet — it explains a gap between an account's total balance and its available balance. # List settled transactions (/docs/api-reference/settlement-reporting/listSettledTransactions) Lists your transactions that have been settled, with a summary of the total settled amount alongside the page of results. Use it for a transaction-level view of settlement; use GET /v3/payment/sett… # List settlement windows (/docs/api-reference/settlement-reporting/listSettlementWindows) Lists your settlement windows, newest first. A window groups the transactions that became ready for settlement on a given date; when it closes it produces one or more batches, and each batch is wha… # Get a settlement window (/docs/api-reference/settlement-reporting/getSettlementWindow) Retrieves one settlement window together with its batches. Each batch carries its own rfsDate (the ready-for-settlement date), channel and method, gross and net amounts, and the fees and VAT taken… # Get a batch within a settlement window (/docs/api-reference/settlement-reporting/getSettlementWindowBatch) Retrieves one batch of one settlement window. This is the last step of reconciling a single order: read the order's settlementWindowId and settlementBatchId from the transaction, then fetch the bat… # Get a settlement batch (/docs/api-reference/settlement-reporting/getSettlementBatch) Retrieves one settlement batch by its id, without needing to know which window it belongs to. Use it when a transaction gave you a settlementBatchId on its own. # Export settlement batch transactions (/docs/api-reference/settlement-reporting/exportSettlementBatches) Exports the transactions inside settlement batches as a spreadsheet, one row per transaction with its selling fees, VAT, settlement amount, ready-for-settlement date, method, and channel. This is t… # List batches (/docs/api-reference/bulk-batches/listBatches) Retrieves the merchant's batches, paginated. currency is required, so a batch list is always scoped to one currency; every other filter is optional. # Create a batch (/docs/api-reference/bulk-batches/createBatch) Creates an empty batch. batchType decides which import sheet the batch expects — withCustomer when each row carries the customer's details so Kashier can create or match a customer record, withoutC… # Get a batch (/docs/api-reference/bulk-batches/getBatch) Retrieves a single batch together with the items it contains, so you can review what was imported before sharing it. # Delete a batch or empty it (/docs/api-reference/bulk-batches/deleteBatch) Deletes a batch, or just its contents. Pass operation=batch to remove the batch itself, or operation=batchitems to empty it and keep the batch so you can re-import a corrected sheet into it. # Update a batch (/docs/api-reference/bulk-batches/updateBatch) Renames an existing batch. The items inside it are left untouched — to change those use updateBatchItem. # Import batch items from a sheet (/docs/api-reference/bulk-batches/importBatchItems) Fills a batch by uploading an Excel sheet of invoices. sheetType must match how the batch was created — withCustomer when each row carries the customer's details, withoutCustomer when the rows are… # Share a batch (/docs/api-reference/bulk-batches/createBatchShare) Turns the batch into live invoices and sends each customer their own payment link, on the channel their row carries. Call this once the imported items look right; the response carries the share rec… # Re-share a batch (/docs/api-reference/bulk-batches/reshareBatch) Re-sends an already-shared batch to the same customers, on the channel used the first time. Useful for chasing a batch of unpaid invoices without rebuilding it. # Get a batch item (/docs/api-reference/bulk-batches/getBatchItem) Retrieves a single item from a batch — one invoice-to-be, with its amount, due date, and status. # Delete a batch item (/docs/api-reference/bulk-batches/deleteBatchItem) Removes a single row from a batch. Use it to drop a customer from a bulk billing run without re-importing the whole sheet. # Update a batch item (/docs/api-reference/bulk-batches/updateBatchItem) Corrects a single row inside a batch before it is shared — most often to fix an amount or a due date that came in wrong from the imported sheet. The fields live under a batchItem wrapper and mirror… # Get a merchant's allowed payment methods (/docs/api-reference/checkout-reference-data/getMerchantPaymentMethods) Retrieves the payment methods your merchant account is entitled to. A custom checkout calls this first and renders only the methods it gets back — the hosted checkout and iframe apply the same enti… # Get a merchant's installment banks (/docs/api-reference/checkout-reference-data/getMerchantInstallmentBanks) Retrieves the banks that offer card installments on the merchant account, with their names in English and Arabic, their abbreviation, and a logo you can render in the checkout. Use the returned ban… # Get one bank's installment plans (/docs/api-reference/checkout-reference-data/getMerchantInstallmentBankPlans) Retrieves the installment plans a single bank offers on the merchant account — the durations the customer can pick from and the minimum amount each plan requires. Returns an empty array when the ba… # Get one bank's installment fees (/docs/api-reference/checkout-reference-data/getMerchantInstallmentBankFees) Retrieves the fee charged per plan duration for a single bank on the merchant account, so a checkout can show the customer what each instalment length costs. The bank is addressed by its abbreviati… # Get one bank's installment fees (/docs/api-reference/fees-and-discounts/getMerchantInstallmentBankFees) Retrieves the fee charged per plan duration for a single bank on the merchant account, so a checkout can show the customer what each instalment length costs. The bank is addressed by its abbreviati… # Calculate the fee on an amount (/docs/api-reference/fees-and-discounts/calculatePaymentFees) Works out the processing fee for an amount under the merchant's pricing model and returns the total the customer would be charged. Use it when fees are passed on to the customer, so your checkout c… # Calculate a discount and the fee together (/docs/api-reference/fees-and-discounts/calculateDiscount) Given an amount and a discount, returns the fee, the discount taken off, and the final amount the customer pays. Unauthenticated so a checkout page can call it while the customer is still choosing… # List your discount campaigns (/docs/api-reference/fees-and-discounts/listDiscountNames) Returns the names of the discount campaigns configured on the merchant account and whether each one is currently active. Campaigns are set up by Kashier rather than through the API, so this is a re… # List a merchant's active discounts (public) (/docs/api-reference/fees-and-discounts/listActiveDiscounts) Returns the discount campaigns that are live right now for a merchant, optionally narrowed to the card BIN the customer is paying with and the channel they are paying on. Unauthenticated, because a… # Check whether a card qualifies for a discount (/docs/api-reference/fees-and-discounts/checkCardDiscount) Checks the card the customer has just entered against the merchant's live bank discount campaigns and, when one applies, returns how much comes off and what the fee would be. This is the call a che… # List banks (/docs/api-reference/constants/listBankConstants) Retrieves the Egyptian banks Kashier publishes, with each name in English and Arabic and its abbreviation. Use it to populate a bank picker or to address a bank in the installment fee lookup. For p… # List cities (/docs/api-reference/constants/listCityConstants) Retrieves the Egyptian governorates Kashier recognises, in English and Arabic, each with the ISO 3166-2 subdivision code to store against an address. # List countries (/docs/api-reference/constants/listCountryConstants) Retrieves every country Kashier recognises, with its two- and three-letter codes, its international dialling prefix, and its flag as an embedded image. Useful for country and phone-prefix pickers.… # List industries (/docs/api-reference/constants/listIndustryConstants) Retrieves the industries and their sectors that a merchant business profile can be classified under, in English and Arabic. # Get payment method definitions (/docs/api-reference/constants/getPaymentMethodDefinitions) Retrieves every payment method Kashier supports, mapped to the provider integrations that can process it. It is the canonical list of method names — card, wallet, bankinstallments, and the rest — u… # List eligible transactions (/docs/api-reference/instant-settlement/listEligibleInstantSettlementTransactions) Returns a paginated list of your not-yet-settled transactions that are eligible for instant settlement, together with summary totals across the whole eligible set and the caps that apply to your ac… # Get amount suggestions (/docs/api-reference/instant-settlement/getInstantSettlementSuggestions) Given a target amount, returns the combination of your eligible transactions whose combined settlementAmount comes closest to it — one combination at or below the target (below) and one at or above… # Inquire about an instant settlement (/docs/api-reference/instant-settlement/inquireInstantSettlement) Works out what you would receive for a set of transactions without committing to it. It runs the same calculation the create-request endpoint runs and returns the same breakdown, but it changes not… # List your instant settlement requests (/docs/api-reference/instant-settlement/listInstantSettlementRequests) Returns a paginated list of your own instant settlement requests, newest first, with the totals and status of each. Use Get request details for the full breakdown of a single request. Requires the… # Create an instant settlement request (/docs/api-reference/instant-settlement/requestInstantSettlement) Creates an instant settlement request over the selected transactions. The request is created in PENDING status and is then reviewed by Kashier: approval moves it to PROCESSING and deducts the early… # Get request details (/docs/api-reference/instant-settlement/getInstantSettlementRequest) Returns the full detail of a single instant settlement request: its totals and fee breakdown, its status history, the transactions an agent excluded from it, and the balance-ledger records created… # Get request transactions (/docs/api-reference/instant-settlement/listInstantSettlementRequestTransactions) Returns the transactions that belong to a specific request, paginated. Rows an agent removed from the request stay on it and come back with selected: false and an unselectedAt timestamp; use the se… # Send a message to a POS terminal (/docs/api-reference/pos-terminals/sendPosTerminalMessage) Relays a JSON message to one of your POS terminals. Kashier authenticates you as the merchant, confirms the terminal belongs to your account, and forwards the message to the terminal in real time o… # Generate a POS payment QR code (/docs/api-reference/pos-terminals/createPosQrCode) Generates a QR code that a customer scans with their phone to open a Kashier hosted payment page for the order, instead of presenting a card at the terminal. The response carries the QR image as a… # List a terminal's transactions (/docs/api-reference/pos-terminals/listPosTerminalTransactions) Retrieves the card-present transactions processed on one of your POS terminals. The gateway passes the request through to the transaction manager unchanged, so the rows carry the same fields as the… # Get one terminal transaction (/docs/api-reference/pos-terminals/getPosTerminalTransaction) Retrieves the details of a single card-present transaction processed on one of your POS terminals. # Get a terminal transaction report (/docs/api-reference/pos-terminals/getPosTerminalReport) Retrieves a transaction report for one POS terminal over a date and time window. Pass all as the terminalId to report across every terminal on your merchant account. # Acknowledge a terminal's transactions (/docs/api-reference/pos-terminals/acknowledgePosTerminalTransactions) Acknowledges the transactions a POS terminal has reported, so they are marked as received and can move on to settlement. Used to close a terminal's batch from your own back office. # Reconcile a terminal's transactions (/docs/api-reference/pos-terminals/reconcilePosTerminal) Reconciles the transactions your system holds for a POS terminal against the transactions Kashier holds, so the two sides agree before settlement. Send the terminal and the totals you expect. # Start a buy-now-pay-later payment on a terminal (/docs/api-reference/pos-terminals/createPosBnplPayment) Starts a buy-now-pay-later (BNPL) card-present operation on one of your POS terminals, optionally against a specific BNPL plan. Availability depends on the BNPL providers enabled on your merchant a… # List your POS branches (/docs/api-reference/pos-terminals/listPosBranches) Retrieves the branches your POS terminals are assigned to, so you can group terminals and reports by physical location. # List terminals (/docs/api-reference/terminals/listPosTerminals) Retrieves the POS terminals registered on your merchant account. The list can be filtered by terminal ID, serial number, branch, label, status, and mode, and is paginated and sortable. # List terminal branches (/docs/api-reference/terminals/listTerminalBranches) Retrieves the branches your terminals are assigned to, with the number of terminals in each. Paginated and searchable. # Export terminals (/docs/api-reference/terminals/exportTerminals) Exports the terminals on your merchant account as an Excel workbook, using the same filters as the terminals list. # Get terminal details (/docs/api-reference/terminals/getTerminal) Retrieves one terminal on your merchant account, including its branch, status, mode, and card-acceptance limits. # List products (/docs/api-reference/products/listProducts) Retrieves the merchant's products. Every filter is optional — call it with no query at all to page through the whole catalogue, or narrow it down by category, stock status, currency, or a free-text… # Create a product (/docs/api-reference/products/createProduct) Adds a single product to the catalogue. Only the name, currency, and unit price are required; set isVariant when the product is a variation of another item, and quantity when you want Kashier to tr… # Get a product (/docs/api-reference/products/getProduct) Retrieves a single product by its record id, including its description, image, price, and stock status. # Update a product (/docs/api-reference/products/updateProduct) Updates an existing product. Send only the fields you want to change — anything you leave out keeps its current value, so this is safe to call from an ERP that only knows about price and stock move… # Get a product's timeline (/docs/api-reference/products/getProductTimeline) Retrieves the audit trail for a product — every create and edit, which fields changed, and which dashboard user made the change. # Assign or unassign categories in bulk (/docs/api-reference/products/assignProductCategories) Adds categories to, or removes them from, a set of products in one call. operation picks the direction, so the same endpoint covers both filing products into a new category and clearing them out of… # Replace the categories on products (/docs/api-reference/products/changeProductCategories) Replaces the full category set on the given products with categoryIds, rather than adding to it. Use this when your ERP is the source of truth and you want Kashier to match it exactly. # Export products (/docs/api-reference/products/exportProducts) Exports the products matching the given filters and returns the Excel workbook directly in the response. The filters are the same ones listProducts accepts. For a large catalogue use exportProducts… # Export products asynchronously (/docs/api-reference/products/exportProductsAsync) Exports the products matching the given filters without holding the request open. The workbook is generated in the background and emailed to email — or to the merchant account's own address when yo… # Upload a product image (/docs/api-reference/products/uploadProductImage) Uploads an image and returns the hosted URL for it. Upload the file first, then pass the returned URL as the product's image when you create or update it. Files must be under 5 MB. # List categories (/docs/api-reference/categories/listCategories) Retrieves the merchant's product categories, paginated. Filter by name with q, or narrow the list to categories created inside a date range. # Create a category (/docs/api-reference/categories/createCategory) Creates a product category. A category only needs a name; products are filed into it afterwards with assignProductCategories or changeProductCategories. # Get a category (/docs/api-reference/categories/getCategory) Retrieves a single category by its record id. # Delete a category (/docs/api-reference/categories/deleteCategory) Deletes a category. Products that were filed into it stay in the catalogue — only the grouping goes away. # Update a category (/docs/api-reference/categories/updateCategory) Renames an existing category. The products filed into it are left untouched. # List a merchant's categories (public) (/docs/api-reference/categories/listSimpleCategories) Returns just the ids and names of a merchant's categories, with no pagination and no authentication. This is the lightweight read a storefront or a payment page uses to render a category picker, so… # Get the categories on a payment page (public) (/docs/api-reference/categories/getPaymentPageCategories) Returns the categories used by the products on a published product payment page, looked up by the page's link identifier. Unauthenticated, because the payment page itself calls it while a customer… # List your Payment API keys (/docs/api-reference/credentials-and-access/listApiKeys) Returns the Payment API keys on your merchant account — the keys used to compute the Kashier-Hash order hash and to verify redirect and webhook signatures. Payment API keys are not bearer credentia… # Create a Payment API key (/docs/api-reference/credentials-and-access/createApiKey) Issues a new Payment API key. Despite being a create, the verb is PUT and the operation is selected by a required operation=apiKey.create query parameter — omit it and the call is rejected. The key… # Delete a Payment API key (/docs/api-reference/credentials-and-access/deleteApiKey) Permanently removes a Payment API key. Anything still computing hashes with it starts failing signature verification immediately, so retire it from your integrations first. Requires the delete-API-… # Validate a set of credentials (/docs/api-reference/credentials-and-access/validateMerchantCredentials) Checks whether the secret keys and Payment API keys you hold are the right ones for the modes you have filed them under. Useful in a deployment check, or when a partner platform stores merchant cre… # List your secret keys (/docs/api-reference/credentials-and-access/listSecretKeys) Returns the secret keys on your account — the credential you put in the Authorization header. Requires the API-key permission on your user's role. Kashier has not published the response shape for t… # Update a user's secret keys (/docs/api-reference/credentials-and-access/updateSecretKeys) Updates the secret keys held for one dashboard user. Requires the API-key permission on your user's role. Treat this as destructive until you have confirmed its behaviour with Kashier. Kashier has… # List the IP allow-list (/docs/api-reference/credentials-and-access/listAllowedIpAddresses) Returns the IP addresses allowed to call Kashier with your secret key. Read this first when a working integration suddenly returns 403. Every secret-key call is checked against this list, and an IP… # Add an IP to the allow-list (/docs/api-reference/credentials-and-access/createAllowedIpAddress) Adds one address to the allow-list. Adding the first entry switches the allow-list on. Until then every IP is allowed; the moment one entry exists, every other IP is refused with 403 Unauthorized I… # Enable or disable the whole allow-list (/docs/api-reference/credentials-and-access/toggleAllAllowedIpAddresses) Turns every entry on the allow-list active or inactive in one call — the escape hatch for when the list has locked you out. Sending isActive: false deactivates every entry, which leaves no active e… # Get one allow-list entry (/docs/api-reference/credentials-and-access/getAllowedIpAddress) Returns a single IP allow-list entry. Requires the view-IP permission on your user's role. # Remove an IP from the allow-list (/docs/api-reference/credentials-and-access/deleteAllowedIpAddress) Deletes one entry. Removing the last entry empties the list, and an empty list allows every IP — deleting entries loosens the restriction rather than tightening it. Requires the delete-IP permissio… # Update an allow-list entry (/docs/api-reference/credentials-and-access/updateAllowedIpAddress) Changes the address or label on an existing entry. Send the whole entry, not just the field you are changing. Requires the edit-IP permission on your user's role. # List your webhooks (/docs/api-reference/webhooks/listWebhooks) Returns every webhook configured for your merchant, across both modes. # Create a webhook (/docs/api-reference/webhooks/createWebhook) Creates a new webhook for your merchant. The webhook's url must be https. There is a cap on how many webhooks you can hold in each mode; once you reach it, creating another webhook in that mode is… # List subscribable events (/docs/api-reference/webhooks/listWebhookEvents) Returns the catalog of event types a webhook can subscribe to — the exact values that are valid in the events array of Create a webhook and Update a webhook. Read this catalog rather than hard-codi… # Delete a webhook (/docs/api-reference/webhooks/deleteWebhook) Removes a webhook. There's no undo — deliveries already recorded against it stay visible in delivery records. # Update a webhook (/docs/api-reference/webhooks/updateWebhook) Updates an existing webhook. Send only the fields you want to change. The https-only URL rule from webhook creation applies here too. Moving a webhook to the other mode re-checks that mode's webhoo… # Get delivery records for a transaction (/docs/api-reference/webhooks/getWebhookRecordsForTransaction) ⚠️ Not yet available on the test environment. This route is not registered on test-api.kashier.io today: calling it there returns a bare 404, not a records payload, while the webhook CRUD endpoints… # Get delivery records for a transfer (/docs/api-reference/webhooks/getWebhookRecordsForTransfer) ⚠️ Not yet available on the test environment. Like its transaction counterpart, this route is not registered on test-api.kashier.io today and returns a bare 404, while the webhook CRUD endpoints on… # Resend a delivery (/docs/api-reference/webhooks/resendWebhookRecord) Re-sends a previously recorded delivery — the original stored request, byte-for-byte, including its original signature — to the same URL. Useful after fixing an endpoint that was down or returning… # Test a webhook (/docs/api-reference/webhooks/testWebhook) ⚠️ Not yet available on the test environment. This route is not registered on test-api.kashier.io today: calling it there returns a bare 404 rather than sending a test delivery, while the webhook C…