# Callbacks

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 in the request; if you did not send one, the merchant's default Callback URL.  The same sale event can also go to the partner's callback URL. 
- 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: `localhost`, private and loopback IP addresses, and (in production) hosts without a valid top-level domain.



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`)

| `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-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. 
- **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. 

> ⚠ TO CONFIRM: The exact `sale_status` string after a partial refund — the existing docs show `partial-refund`, while PayMe's API code suggests `partially-refunded`. Which is it (and the matching void strings)? [Q37]

## 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](/marketplaces-and-platforms/lists-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 |



## Other callbacks

### Subscriptions

Sent to `sub_callback_url`, or to the partner's callback URL if none 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 billing iteration was paid (adds `payme_sale_id` and `payme_sale_code`) |
| `sub-iteration-skipped` | A billing iteration was skipped |
| `sub-failure` | The subscription failed |
| `sub-pause` | The subscription was paused |
| `sub-cancel` | The subscription was cancelled |
| `sub-complete` | The subscription completed |

The payload includes `sub_payme_id`, `sub_status`, `sub_price`, iteration counters and buyer fields. Each iteration also creates a sale, which sends its own `sale-complete` or `sale-failure`. 

### Israeli direct debit

- `sale-paid` when the debit is submitted, with `buyer_key` and `buyer_status`.
- 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* (not authorization) sends no callback on success and can take up to 8 days. 

### 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). 

### Bank transfer

A bank transfer reaching `PAID` is reported with a callback. 

> ⚠ TO CONFIRM: Bank transfers most likely use the normal `sale-complete` / `sale-failure` callbacks with no dedicated `notify_type`. Please confirm. [Q34]

### B2B payment contracts

A contract's `callback_url` receives a callback for every change in status. 

## Verifying a callback (`payme_signature`)

### How it is computed

`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
3. `payme_transaction_id` (from the payload)
4. `payme_sale_id` (from the payload)

It is a plain MD5 hash, not an HMAC. 

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", ""))
```



> ⚠ TO CONFIRM: What is the "client password" called in PayMe's onboarding material, how does a partner receive it, and how can a merchant that has no `payme_client_key` verify `payme_signature`? [Q1]

### 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:

- Subscription callbacks have no signature. The subscription **return URL** is signed, with `sub_payme_id` in place of `payme_sale_id`. 
- `verify_only` refunds return `payme_signature: null`. 
- B2B payment contracts use a different formula. 

### 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. 

> ⚠ TO CONFIRM: Do callbacks come from a fixed set of IP addresses that merchants can allow-list? [Q3]

## Delivery, retries, duplicates and ordering

### What counts as delivered

- Any HTTP status **below 400** counts as delivered. The response body is ignored, and redirects are followed.
- 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 endpoint keeps failing, PayMe pauses delivery to it for a short time, and callbacks during that time are skipped.

**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

> ⚠ TO CONFIRM: Callbacks are most likely **not** delivered 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. Please confirm, so the docs can state it as a rule. Until then: never let a later-arriving callback overwrite a final state you already recorded, and query the sale when in doubt. [Q33]

## Handling checklist

1. Parse the body as form data and reply `200` within 5 seconds.
2. 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.
