# Create a sale with 3D Secure Building with an AI agent? Start at https://docs.payme.io/agents.md - Sandbox: `POST https://sandbox.payme.io/api/generate-sale` - Production: `POST https://live.payme.io/api/generate-sale` ## Credentials - Request body field `seller_payme_id` (required): The seller's MPL. Send it in the JSON body: `generate-sale` doesn't read the `PayMe-Merchant-Key` header, so a request without a body MPL returns error `1`. Partners may send `seller_id` with `payme_client_key` instead. - Request body field `payme_client_key` (optional): Partners only: your partner key (or send the `PayMe-Partner-Key` header). With `seller_payme_id`, the seller must belong to you, otherwise error `20`. ## Overview Creates a sale on the hosted payment page with 3D Secure. The call is an ordinary [`generate-sale`](/payments/hosted-payment-page/post-generate-sale) that returns `sale_url`; 3D Secure runs inside PayMe's payment page. `POST https://sandbox.payme.io/api/generate-sale` (production: `https://live.payme.io/api/generate-sale`), with a JSON body (`Content-Type: application/json`). ### Credentials Send `seller_payme_id` (the MPL) in the JSON body: this endpoint doesn't read the `PayMe-Merchant-Key` header, so a request without a body MPL returns error `1`. Partners add `payme_client_key` (or the `PayMe-Partner-Key` header) and may name the seller by `seller_id` instead. See [Authentication](/payments/getting-started/authentication). ### When 3D Secure runs 3D Secure must be enabled on the account by PayMe. PayMe's system rules then apply it to card payments that match any of them: a card issued in a country other than the seller's registered country, 3 or more installments, or an amount of at least the account's 3D Secure threshold. The amount rule compares `sale_price` in minor units with the threshold, without converting currencies. To add rules of your own, ask your onboarding manager before you go live. ### Force it on or off for one sale Send `services` with the `3DSecure` service. This needs the 3D Secure service on the account. ```json { "services": [{"name": "3DSecure", "settings": {"active": true}}] } ``` - `"active": true` forces 3D Secure on this sale. - `"active": false` turns off your account's own 3D Secure rules for this sale. PayMe's system rules (foreign card, 3 or more installments, the amount threshold) still apply; only PayMe can switch those off for the account. - A setting for a service the account doesn't have is ignored, without an error. Each entry needs `settings`: an entry without it returns error `1` after the sale has already been created. ### When authentication fails When the buyer fails the 3D Secure challenge, the sale becomes `failed` and PayMe sends the buyer to the first of these that is set: your `sale_return_url` (with `payme_status=error`), then `cancel_url`, then PayMe's payment page to try another card. ### Check the result Query the sale with [`get-transactions`](/payments/query-apis/post-get-transactions) or [`get-sales`](/payments/query-apis/post-get-sales) and read `sale_is_3ds`. The sale callback carries `sale_3ds` (`1` when 3D Secure was used). Sandbox test cards for the challenge are on [Test Cards and Payment Methods](/payments/getting-started/test-cards-and-payment-methods). ### Rules for every request - Amounts are in minor units (`1000` = 10.00). - Never put card numbers in any field. A value (other than `transaction_id` and `order_number`) that contains a 14–19-digit number passing the card checksum is refused with error `20` (HTTP 400), " contains invalid data". - Don't send the legacy `price` field: it is in major units and, when numeric, overrides `sale_price`. - PayMe doesn't de-duplicate `transaction_id`: every call creates a new sale. See [Idempotency and Retries](/payments/getting-started/idempotency-and-retries). - Errors return `status_code: 1` with `status_error_code`, most with HTTP 500. See [Errors](/payments/getting-started/errors). ### Callbacks When the sale is paid, PayMe sends a `POST` with an `application/x-www-form-urlencoded` body to `sale_callback_url`. The notification types and fields are on [Callbacks](/payments/getting-started/callbacks). ## Parameters This endpoint takes no path, query or header parameters. ## Request body Content type: `application/json` | Field | Type | Required | Description | | --- | --- | --- | --- | | `seller_payme_id` | string | yes | The seller's MPL. Send it in the JSON body: `generate-sale` doesn't read the `PayMe-Merchant-Key` header, so a request without a body MPL returns error `1`. Partners may send `seller_id` with `payme_client_key` instead. | | `payme_client_key` | string | no | Partners only: your partner key (or send the `PayMe-Partner-Key` header). With `seller_payme_id`, the seller must belong to you, otherwise error `20`. | | `seller_id` | string | no | Partners only: your own ID for the seller, set in `create-seller`, sent instead of `seller_payme_id`. Requires `payme_client_key`. | | `sale_price` | integer | yes | Sale amount in minor units: 50.75 is sent as `5075`. The default allowed range is `500`–`4000000`, the same in every currency; PayMe can set a different range for a partner. A missing, non-numeric or out-of-range value returns error `352`. Decimals are rounded. | | `currency` | string | yes | Sale currency, a 3-letter [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) code such as `ILS`, enabled on the account (otherwise error `252`). Required for `sale` and `authorize`; a `token` or `template` sale without it uses the account's default currency. | | `product_name` | string | yes | Short description of the product or service, up to 500 characters. Shown to the buyer, and on the invoice when the invoicing service is active. Missing returns error `353`. | | `transaction_id` | string | no | Your own order reference, up to 50 characters. PayMe returns it in responses and callbacks but doesn't check it for uniqueness. | | `installments` | string | no | Number of payments. `1` (default) or `0`: one payment. `2`–`12`: that fixed number. `102`–`112`: the buyer chooses on the page, up to the last two digits (`106` = up to 6). More than one installment is ILS only (`312`) and works only with `credit-card` or `multi` (other methods return `351`). On a `multi` page that lets the buyer choose, a buyer who pays with Apple Pay or Google Pay pays in one payment. Fixed values above 12 are accepted when the sale is created, but the payment fails unless PayMe has enabled more installments on your account. A non-numeric value returns `10`, any other unaccepted value `351`, and an ILS price lower than the number of installments `358`. | | `sale_payment_method` | string | no | Payment method. 3D Secure applies to card payments: `credit-card`, or a card chosen on a `multi` page. Default `credit-card`. | | `services` | array | no | Per-sale service settings. For 3D Secure: `[{"name": "3DSecure", "settings": {"active": true}}]` forces it, `"active": false` turns off your own rules for this sale. Each object needs `settings` (error `1`). Unknown or inactive services are ignored. | | `cancel_url` | string | no | Where to send the buyer when 3D Secure authentication fails and no `sale_return_url` is set. Without either, the buyer goes back to the payment page. Also the PayPal cancel address. Must be a public URL (error `21`). | | `market_fee` | number | no | The partner's (marketplace's) fee: a percentage of the sale price, VAT included, from `0` to `60`. It is added on top of PayMe's fees and transferred to the marketplace once a month. Default: the seller's market fee set at `create-seller`. A non-numeric value returns `10`, a negative one `304`, and more than 60 `308`. Ignored for sellers paid directly. | | `sale_callback_url` | string | no | Where PayMe sends server-to-server callbacks for this sale, as a form-encoded `POST`; see [Callbacks](/payments/getting-started/callbacks). If omitted, PayMe uses your partner's default callback URL, if one is set; a seller has no dashboard setting for it, so send it on every call. Must be a public address: `localhost`, private or loopback IP addresses and IPv6 addresses are refused with error `21`, which fails the whole call. | | `sale_return_url` | string | no | Where PayMe redirects the buyer (the iframe or the full page) after a successful payment, with the sale details as query parameters; see [Hosted Payment Page options](/accepting-payments/checkout-and-in-person/hosted-payment-page-options). If omitted, your partner's default return URL is used, if one is set. Same address rules as `sale_callback_url` (error `21`). | | `sale_send_notification` | boolean | no | `true`: PayMe sends the buyer a payment request with the payment link, by email to `sale_email` and by SMS to `sale_mobile`. Default `false`. | | `sale_email` | string | no | Buyer's email. Pre-fills the payment page and receives the payment request when `sale_send_notification` is `true`. | | `sale_mobile` | string | no | Buyer's mobile number. Pre-fills the payment page and receives the SMS payment request when `sale_send_notification` is `true` and the SMS service is active on the account. | | `sale_name` | string | no | Buyer's full name. Pre-fills the payment page (split at the first space into first and last name) and is used in notifications. | | `capture_buyer` | string | no | `1` or `true` to save the buyer's card as a token after the payment; the token arrives as `buyer_key` in the callback. Omit it, or send `0`, to skip. Works with `credit-card`, `apple-pay`, `il-direct-debit` and `multi` (other methods return `4`). Can't be combined with `buyer_key` (`601`). | | `buyer_is_private` | boolean | no | With `capture_buyer` or a token sale: `true` ties the token to this seller, so `get-buyer-key` works with the seller's MPL. Without it the token is account-level: any seller of the same partner can charge it, and a `get-buyer-key` call that sends `seller_payme_id` fails with error `603`. | | `buyer_perform_validation` | boolean | no | With `capture_buyer` or a token sale: whether PayMe validates the card with the issuer before saving the token. Default `true`. Sending `true` without `capture_buyer` or a token sale returns `608`. | | `buyer_key` | string | no | Charges a saved token at once: no payment page and no `sale_url` (see the "Token charged" response). The token comes from a callback, [`get-buyer-key`](/payments/tokenization/post-get-buyer-key) or Hosted Fields `tokenize()`. Can't be combined with `capture_buyer` (`601`), `sale_type` `token`/`seller-token` (`602`) or `template` (`606`). | | `layout` | string | no | Payment page layout: `dynamic` (default), `dynamic-loose` (no social ID or CVV validation), `qr-sms` (Bit: QR code or SMS), `token-capture` (save a card without a charge; requires `sale_type: token`). Other values fall back to `dynamic`. | | `language` | string | no | Language of the payment page and the error messages. Default `he`. `he` and `en` are fully supported; `es`, `ru` and `ar` are accepted, but messages and the payment page are then mostly in English. Case-insensitive; any other value, or an empty string, returns error `51`. | ### Request example ```json { "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_price": 60000, "currency": "ILS", "product_name": "Laptop", "transaction_id": "order-1002", "installments": "1", "sale_callback_url": "https://your-server.example.com/payme/callback", "sale_return_url": "https://your-site.example.com/payment/success", "cancel_url": "https://your-site.example.com/checkout", "language": "en", "services": [ { "name": "3DSecure", "settings": { "active": true } } ] } ``` ## Code samples ### curl ```bash curl -X POST 'https://sandbox.payme.io/api/generate-sale' \ -H 'Content-Type: application/json' \ --data-raw '{ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_price": 60000, "currency": "ILS", "product_name": "Laptop", "transaction_id": "order-1002", "installments": "1", "sale_callback_url": "https://your-server.example.com/payme/callback", "sale_return_url": "https://your-site.example.com/payment/success", "cancel_url": "https://your-site.example.com/checkout", "language": "en", "services": [ { "name": "3DSecure", "settings": { "active": true } } ] }' ``` ### Node.js ```javascript const response = await fetch("https://sandbox.payme.io/api/generate-sale", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_price": 60000, "currency": "ILS", "product_name": "Laptop", "transaction_id": "order-1002", "installments": "1", "sale_callback_url": "https://your-server.example.com/payme/callback", "sale_return_url": "https://your-site.example.com/payment/success", "cancel_url": "https://your-site.example.com/checkout", "language": "en", "services": [ { "name": "3DSecure", "settings": { "active": true } } ] }), }); console.log(await response.text()); ``` ### Python ```python import requests response = requests.post( "https://sandbox.payme.io/api/generate-sale", json={ "seller_payme_id": "MPLDEMO-MPLDEMO-MPLDEMO-1234567", "sale_price": 60000, "currency": "ILS", "product_name": "Laptop", "transaction_id": "order-1002", "installments": "1", "sale_callback_url": "https://your-server.example.com/payme/callback", "sale_return_url": "https://your-site.example.com/payment/success", "cancel_url": "https://your-site.example.com/checkout", "language": "en", "services": [ { "name": "3DSecure", "settings": { "active": True, }, }, ], }, ) print(response.text) ``` ### PHP ```php 'POST', CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => $body, ]); $response = curl_exec($ch); curl_close($ch); echo $response; ``` ## Responses ### 200 — Sale created (success) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `0` success, `1` error. | | `sale_url` | string | PayMe's payment page for this sale. Show it to the buyer in an iframe or a redirect, exactly as returned. It stays payable while the sale is `initial` or `failed`, until `sale_expiry_seconds` passes if you sent it. | | `payme_sale_id` | string | PayMe's sale ID. Store it: refunds, captures and queries use it. | | `payme_sale_code` | integer | Short numeric sale code, for display only. | | `price` | integer | Sale amount in minor units (`100` for a token sale). | | `transaction_id` | string | Your order reference, as sent. | | `currency` | string | Sale currency (ISO 4217). | | `sale_payment_method` | string | Payment method of the sale, for example `credit-card` or `multi`. | | `session` | string | Request ID. Quote it when you contact PayMe. | ```json { "status_code": 0, "session": "AtsJ0as5", "sale_url": "https://sandbox.payme.io/sale/generate/SALE1700-000000AA-EXAMPLE1-SALE0001", "payme_sale_id": "SALE1700-000000AA-EXAMPLE1-SALE0001", "payme_sale_code": 12345678, "price": 60000, "transaction_id": "order-1002", "currency": "ILS", "sale_payment_method": "credit-card" } ``` ### 500 — Request refused. Most request errors return HTTP 500 with `status_code: 1`; some return 400, 403, 422 or 429. See [Errors](/payments/getting-started/errors). (error) | Field | Type | Description | | --- | --- | --- | | `status_code` | integer | `1` for an error. | | `status_error_code` | integer | PayMe's error code, for example `251` (seller not found) or `352` (invalid price). | | `status_error_details` | string | The error message, in the requested `language` (Hebrew by default). | | `status_additional_info` | string | Extra detail, usually the name of the field at fault. | | `session` | string | Request ID. Quote it when you contact PayMe. | ```json { "status_code": 1, "status_error_details": "Seller was not found", "status_additional_info": "seller_payme_id", "status_error_code": 251, "session": "AtsJ0as5" } ```