# Callbacks Building with an AI agent? Start at https://docs.payme.io/agents.md ## Related - [Callback types reference](/payments/getting-started/callback-types) - [Sale Statuses](/payments/codes-statuses/sale-statuses) - [List sales](/payments/query-apis/post-get-sales) - [Idempotency and Retries](/payments/getting-started/idempotency-and-retries) - [Quickstart](/payments/getting-started/quickstart) A callback (PayMe also calls it an "IPN") is a request PayMe's server sends to yours when something happens to a sale, a subscription or a seller. **In short:** reply quickly with HTTP 200, expect repeats and out-of-order delivery, verify `payme_signature`, and confirm the sale's status and amount with [`POST /get-sales`](/payments/query-apis/post-get-sales) before you fulfil an order. ## Format - Method and encoding: `POST` with an `x-www-form-urlencoded` body — not JSON. - Destination: the `sale_callback_url` you sent. If you omit it, PayMe uses your partner's default callback URL, if your partner has one; a seller has no dashboard setting for it, so always send `sale_callback_url` (and `sub_callback_url` for subscriptions). One sale can notify two URLs: the sale's callback URL and the partner's sale callback URL (one delivery per host). - The `language` field of the request sets the language of the callback. ### Callback URL rules - `http://` and `https://` are both accepted. Use HTTPS. - Non-standard ports are allowed (for example `:8443`). - Rejected, with error `21` for the whole call: `localhost` and other hosts without a dot, IP addresses in private or loopback ranges, all IPv6 addresses, and hosts whose top-level domain isn't letters only. Ports must be 2–5 digits. - PayMe doesn't resolve the host, so a valid-looking name is accepted even if it doesn't exist. `sale_return_url` follows the same rules. The callback is separate from the **return URL** redirect. The return URL is a browser `GET` that takes the buyer to your result page; the callback is server-to-server. ## Sale notification types (`notify_type`) Every `notify_type` PayMe sends, with when it fires and its key fields, is listed on [Callback types reference](/payments/getting-started/callback-types). | `notify_type` | Meaning | |---------------|---------| | `sale-complete` | The sale was paid successfully | | `sale-authorized` | The sale was authorized successfully | | `refund` | The sale was refunded — full refund, partial refund, or void of an authorization | | `sale-failure` | A payment attempt failed | | `sale-chargeback` | The sale was charged back | | `sale-chargeback-refund` | The chargeback was reverted | | `sale-create` | The sale was created. Sent only when `generate-sale` included `sale_send_creation_callback: true`; carries `sale_url`. | | `sale-paid` | Israeli direct debit only: the debit was submitted to the bank. | | `sale-il-direct-debit-update` | Israeli direct debit only: the bank authorization status changed. | - **`sale-failure` is sent once per failed attempt**, not once per sale. A buyer can retry on the payment page; each failure sends another `sale-failure` with a different `payme_transaction_id`. They stop once the sale completes. So `failed` isn't final: the sale can still move to `completed`, or `authorized` for a capture sale. - **Refund callbacks:** `payme_transaction_total` is the amount of this refund, `price` is the original sale price, `sale_refund_buffer` is the amount still refundable, and `payme_transaction_id` / `payme_signature` refer to the refund transaction. After a partial refund `sale_status` is `partial-refund`. A sale fully refunded in parts stays `partial-refund` and never moves to `refunded`, so track the refunded total yourself. ## Sale callback payload | Field | Example | Description | |-------|---------|-------------| | `status_code` | `0` | `0` success, `1` error | | `status_error_code` | | In case of an error, PayMe's error code | | `status_error_details` | | In case of an error, the error message | | `notify_type` | `sale-complete` | Notification type (table above) | | `sale_created` | `2016-01-01 15:15:15` | Sale creation time | | `transaction_id` | `12345` | Your order ID | | `payme_sale_id` | `XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX` | PayMe's sale ID | | `payme_sale_code` | `12345678` | Sale code, for display only | | `payme_transaction_id` | `XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX` | PayMe's ID for this transaction (payment, capture, refund or failed attempt) | | `price` | `1000` | Amount in minor units (50.75 → `5075`) | | `currency` | `USD` | ISO 4217 code | | `sale_status` | `completed` | See [Sale Statuses](/payments/codes-statuses/sale-statuses) | | `payme_transaction_card_brand` | `Visa` | Card brand | | `payme_transaction_auth_number` | `01A2B3C` | Authorization number from the card company | | `buyer_card_mask` | `458045******4580` | Masked card number | | `buyer_card_exp` | `0118` | Card expiry | | `buyer_name`, `buyer_email`, `buyer_phone`, `buyer_social_id` | | Buyer details | | `buyer_key` | `XXXXXXXX-…` | Token for future payments; only when `capture_buyer` was set | | `installments` | `1` | Number of installments | | `sale_paid_date` | `2016-01-01 15:16:15` | When the payment happened (not when the callback was sent) | | `sale_release_date` | `2016-01-08 15:15:15` | Release date | | `is_token_sale` | `0` | `0` false, `1` true | | `payme_signature` | `75e99dbcb25cdfbe1c62f0b9376f4144` | Signature (see below) | | `sale_invoice_url` | `https://www.example.com/XXXXXX.pdf` | Invoice URL, if the seller enabled invoices | | `sale_payment_method` | `credit-card` | Payment method name; see [Available Payment Methods](/payments/wallets-apms) | | `sale_type` | `Sale` | Sale type name: `Sale` (also for authorizations), `Token`, `SaleFromTemplate`, `SaleFromSubscription`, … See [Sale Types](/payments/codes-statuses/sale-types) | | `payme_transaction_total` | `1000` | Amount of this transaction, in minor units | | `buyer_card_is_foreign` | `0` | `1` when the card was issued outside Israel | | `sale_3ds` | `0` | `1` when the payment went through 3D Secure | | `buyer_status` | `1` | With `buyer_key`: the status of the saved token | Fields with no value are left out of the body. ## Other callbacks ### Subscriptions Sent to `sub_callback_url`; if it is empty, to your partner's default callback URL (`merchant_callback_url`), if one is set. **Subscription callbacks carry no `payme_signature`.** | `notify_type` | When | |---------------|------| | `sub-create` | The subscription was created | | `sub-active` | The subscription became active | | `sub-iteration-success` | A charge succeeded: first, regular or after a retry, with an identical body in every case (adds `payme_sale_id` and `payme_sale_code`; the charge time is `sub_prev_date`) | | `sub-iteration-skipped` | A billing iteration was skipped | | `sub-failure` | A payment attempt failed. If the card-validation transaction failed when the buyer paid the subscription, its status is `4` and PayMe does not retry. If a scheduled charge failed and will be retried, its status is `7` and `sub_next_date` moves one day. Carries `sub_error_text`, but no sale id, attempt id or failed-attempt counter | | `sub-pause` | The subscription was paused | | `sub-cancel` | The subscription was cancelled, by hand or automatically after the last failed attempt (status `5`). Always contains `seller_payme_id`, `seller_id`, `sub_payme_id`, `sub_payme_code`; `subscription_id` only if you set it | | `sub-complete` | The subscription completed | The payload includes `sub_payme_id`, `sub_status`, `sub_price`, iteration counters and buyer fields (card mask, name, email, phone and ID; never the `buyer_key`). The iteration counters count successful charges only. Each successful iteration also creates a sale, which sends its own `sale-complete` or `sale-failure`. Handlers must be idempotent: re-query [`get-subscriptions`](/payments/query-apis/post-get-subscriptions) before acting. Charge retries (once a day, up to 7 attempts) are described on [Recurring Payments](/payments/saved-cards-recurring). `sub_callback_url` is sent on every successful charge while the subscription is active and on every failed attempt that leads to a retry. #### Subscription callback payload | Field | Example | Description | |-------|---------|-------------| | `notify_type` | `sub-iteration-success` | Notification type (table above) | | `status_code` | `0` | `0` success, `1` error | | `status_error_code` | | In case of an error, PayMe's error code | | `sub_error_text` | | In case of an error, the error message | | `seller_payme_id` | `MPLDEMO-MPLDEMO-MPLDEMO-1234567` | PayMe's seller ID | | `seller_id` | `12345` | Your seller ID | | `sub_payme_id` | `XXXXXXXX-XXXXXXXX-XXXXXXXX-XXXXXXXX` | PayMe's subscription ID | | `sub_payme_code` | `1234` | PayMe's subscription code | | `sub_created` | `2016-01-01 15:15:15` | Subscription creation time | | `sub_payment_date` | `2016-01-01 18:32:23` | Subscription payment time | | `sub_start_date` | `2016-01-02 00:00:00` | When the first iteration was charged | | `sub_next_date` | `2016-01-03 00:00:00` | Next iteration time | | `sub_status` | `1` | See [Subscription Statuses](/payments/codes-statuses/subscription-statuses) | | `sub_iteration_type` | `1` | See [Subscription Iteration Types](/payments/codes-statuses/subscription-iteration-types) | | `sub_currency` | `USD` | ISO 4217 code | | `sub_price` | `10000` | Price of one iteration in minor units (50.75 → `5075`) | | `sub_iterations` | `4` | Number of iterations | | `sub_iterations_completed` | `4` | Completed iterations | | `sub_iterations_left` | `4` | Remaining iterations | | `buyer_card_mask` | `458045******4580` | Masked card number | | `buyer_name`, `buyer_email`, `buyer_phone`, `buyer_social_id` | | Buyer details | ### Israeli direct debit - `sale-paid` when the debit is submitted. Take the token (`buyer_key`) from `sale-il-direct-debit-update` or `get-sales`, not from `sale-paid`. - When the bank authorization status changes: `sale-complete` or `sale-failure`, then `sale-il-direct-debit-update` with `authorization_status` and `authorization_error_code`. The `authorization_status` values are listed on [Errors](/payments/getting-started/errors) (section "Israeli direct debit"). An Israeli direct-debit *payment* sends `sale-paid` when submitted, then `sale-complete` about 8 days later, or `sale-failure` if the bank returns the debit before that. ### Sellers (partners) Sent to the partner's callback URL with all `seller_*` and `fee_*` fields: `seller-create` (adds `seller_public_key`), `seller-update`, `seller-approve`. Related partner notifications: `withdrawal-complete`, `charge-complete`, and `point-of-sale-action-*` (for example after a POS device is paired). #### Seller callback payload | Field | Example | Description | |-------|---------|-------------| | `status_code` | `0` | `0` success, `1` error | | `status_error_code` | | In case of an error, PayMe's error code | | `status_error_details` | | In case of an error, the error message | | `notify_type` | `seller-create` | Notification type (above) | | `seller_payme_id` | `MPLDEMO-MPLDEMO-MPLDEMO-1234567` | PayMe's seller ID | | `seller_id` | `12345` | Your seller ID | | `seller_created` | `2016-01-01 15:15:15` | Seller creation time | | `seller_first_name` | `First` | First name of the account owner | | `seller_last_name` | `Last` | Last name of the account owner | | `seller_social_id` | `999999999` | Social ID of the account owner | | `seller_birthdate` | `1989-05-06` | Birth date, `yyyy-mm-dd` (the request sends DD/MM/YYYY) | | `seller_gender` | `0` | `0` male, `1` female | | `seller_email` | `personal@example.com` | Seller's personal email | | `seller_contact_email` | `contact@example.com` | Contact email shown to buyers | | `seller_phone` | `0500000042` | Seller's personal phone | | `seller_contact_phone` | `031234567` | Contact phone shown to buyers | | `seller_inc` | `2` | See [Seller Incorporation Types (IL)](/marketplaces-and-platforms/seller-reference/seller-incorporation-types-il) | | `seller_inc_code` | `123456` | Business ID (ח.פ, ע.מ) | | `seller_merchant_name` | `Baby Ducks` | Merchant name | | `seller_site_url` | `www.babyducks.com` | Site URL | | `seller_address_city` | `Tel Aviv` | Business address: city | | `seller_address_street` | `Rothschild` | Business address: street | | `seller_address_street_number` | `45` | Business address: street number | | `seller_address_country` | `IL` | Business address: country (ISO 3166-1 alpha-2) | | `seller_active` | `1` | `0` inactive, `1` active | | `seller_approved` | `1` | `0` not approved, `1` approved | | `seller_currencies` | `[ILS, USD, EUR]` | Allowed currencies | ### Bank transfer A bank transfer reaching `PAID` is reported with a callback. There is no bank-transfer-specific `notify_type`: the standard sale callbacks apply (`sale-complete` when the transfer is paid, `sale-failure` otherwise). ### B2B payment contracts Documented with the B2B Payments API: [Contracts: getting started → Callbacks](/b2b-payments/contracts/overview#callbacks). ## Verifying a callback (`payme_signature`) ### How it is computed Partners, who hold `payme_client_key`, verify callbacks with this formula; a seller without a partner key re-queries the sale instead (see below). `payme_signature` is the **lowercase hexadecimal MD5** hash of four values joined with **no separator**, in this order: 1. `payme_client_key` (the partner key) 2. the client password issued together with the client key: the partner's `merchant_password` (called `payme_merchant_secret` in older PayMe documentation) 3. `payme_transaction_id` (from the payload) 4. `payme_sale_id` (from the payload) It is a plain MD5 hash, not an HMAC. The signature always uses your primary client key. If PayMe is moving you to a new client key, your API calls can already use the new key, but the signature stays on the primary key until PayMe completes the switch. Worked example (fake values): | Input | Value | |-------|-------| | `payme_client_key` | `demo_partner_KEY12345` | | client password | `demoPassw0rd` | | `payme_transaction_id` | `TRAN1700-000000AA-EXAMPLE1-TXN00001` | | `payme_sale_id` | `SALE1700-000000AA-EXAMPLE1-SALE0001` | Joined string: `demo_partner_KEY12345demoPassw0rdTRAN1700-000000AA-EXAMPLE1-TXN00001SALE1700-000000AA-EXAMPLE1-SALE0001` `payme_signature` = `f8ffc5bb45fe6c3ad326c439f1a21c28` ```python import hashlib, hmac def payme_signature_ok(form, client_key, client_password): expected = hashlib.md5( (client_key + client_password + form["payme_transaction_id"] + form["payme_sale_id"]).encode() ).hexdigest() return hmac.compare_digest(expected, form.get("payme_signature", "")) ``` The client password is issued together with your partner credentials by PayMe Partners (). A merchant that has no `payme_client_key` cannot compute the signature: verify the sale instead with [`POST /get-sales`](/payments/query-apis/post-get-sales) or [`POST /get-transactions`](/payments/query-apis/post-get-transactions), which is also the check PayMe support recommends. ### Where the same signature appears The same formula is used in sale callbacks, the return-URL redirect (including when `payme_status=error`), and the `capture-sale` and `refund-sale` responses. In each case `payme_transaction_id` is the transaction in that payload — the payment, the capture or the refund. Exceptions: - Seller callbacks and `withdrawal-complete` have no signature. Ignore the body and re-read with `get-sellers` or `get-withdrawals`. - Subscription callbacks have no signature. The subscription **return URL** is signed: the MD5 of the client key, the client password, the URL's `transaction_id` (PayMe's transaction ID; this URL has no `payme_transaction_id`) and `sub_payme_id`. - `verify_only` refunds return `payme_signature: null`. - B2B payment contracts use a shorter formula: the MD5 of the client key, the client password and the contract's `guid`. See [B2B Payments](/b2b-payments/contracts/overview#callbacks). ### Confirm the sale before you fulfil A valid signature tells you the callback refers to a real PayMe sale and transaction. Treat the status and amount in a callback as a notification, not as proof of payment. **Safe practice:** before you fulfil an order, call [`POST /get-sales`](/payments/query-apis/post-get-sales) with `sale_payme_id` and check `sale_status` and `sale_price` against your order. [`POST /get-transactions`](/payments/query-apis/post-get-transactions) works too, and shows each charge and refund as a row, which is why the [AI Toolkit](/agents) steps use it. Find the order by the `payme_sale_id` you stored when you created the sale, and match it on your own order ID as well as the amount. Otherwise a real, cheap sale's ID could be replayed in a forged callback against an expensive order. If you confirm with `get-transactions` instead, send `transaction_type: 40` for a sale that is authorized and not yet captured: the default query doesn't return it. PayMe does not publish a fixed list of callback source addresses, so do not allow-list by IP. Authenticate callbacks by their signature and confirm the sale with `get-sales`. ## Delivery, retries, duplicates and ordering ### What counts as delivered - Any status below 400 counts as delivered. The response body is ignored. - Each attempt times out after **5 seconds**. Reply `200` immediately and do the slow work (emails, fulfilment) afterwards. ### Retries - PayMe makes **up to 3 attempts**: the first, a retry about 30 seconds later, and a last retry about 60 seconds after that. - A 4xx or 5xx response, a timeout, or a connection error triggers a retry. - After the last attempt the callback is **dropped**. It cannot be replayed through the API. - If your host keeps returning 5xx, 408 or 429 responses, timing out or refusing connections, PayMe pauses deliveries to that host for a short time. Callbacks during the pause are not delivered or retried. - A pause is triggered by 5xx responses, `429`, `408`, timeouts and connection errors on your host; an ordinary 4xx (for example a signature or authentication rejection by your code) never causes one. It lifts by itself once your endpoint answers. You can't reset it yourself; if you need it cleared at once, contact PayMe support with the callback host. There is no test ping. Acknowledge first (cold starts included) and process in the background. - Order is not guaranteed: act on `notify_type` and the sale status, not on the order of arrival. **Do not rely on callbacks alone.** Reconcile with [`POST /get-sales`](/payments/query-apis/post-get-sales). ### Duplicates The same callback can arrive more than once — typically when your endpoint processes it but replies after the 5-second timeout. There is no event ID. De-duplicate on **`notify_type` + `payme_sale_id` + `payme_transaction_id`**; each refund, capture and failed attempt has its own `payme_transaction_id`. ### Ordering Callbacks are **not** guaranteed to arrive in order: they are sent by parallel workers with retry delays, so a `refund` can arrive before a `sale-complete` that is still being retried, and there is no sequence number or event timestamp. Never let a later-arriving callback overwrite a final state you already recorded, and query the sale when in doubt. ## Handling checklist 1. Parse the body as form data and reply `200` within 5 seconds. 2. If you are a partner, verify `payme_signature` (sale callbacks). 3. Ignore it if you already processed this `notify_type` + `payme_sale_id` + `payme_transaction_id`. 4. Before fulfilling, confirm `sale_status` and `sale_price` with `POST /get-sales`. 5. Run a periodic reconciliation with `get-sales`, because callbacks can be dropped after 3 attempts.