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 before you fulfil an order.
POST with an x-www-form-urlencoded body — not JSON. 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. language field of the request sets the language of the callback.http:// and https:// are both accepted. Use HTTPS.:8443).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.
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. 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_statusstring after a partial refund — the existing docs showpartial-refund, while PayMe's API code suggestspartially-refunded. Which is it (and the matching void strings)? [Q37]
| 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 |
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 |
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.
sale-paid when the debit is submitted, with buyer_key and buyer_status.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 (section "Israeli direct debit").An Israeli direct-debit payment (not authorization) sends no callback on success and can take up to 8 days.
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).
A bank transfer reaching PAID is reported with a callback.
⚠ TO CONFIRM: Bank transfers most likely use the normal
sale-complete/sale-failurecallbacks with no dedicatednotify_type. Please confirm. [Q34]
A contract's callback_url receives a callback for every change in status.
payme_signature)payme_signature is the lowercase hexadecimal MD5 hash of four values joined with no separator, in this order:
payme_client_key (the partner key)payme_transaction_id (from the payload)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
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_keyverifypayme_signature? [Q1]
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:
sub_payme_id in place of payme_sale_id. verify_only refunds return payme_signature: null. 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 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]
200 immediately and do the slow work (emails, fulfilment) afterwards.Do not rely on callbacks alone. Reconcile with POST /get-sales.
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.
⚠ TO CONFIRM: Callbacks are most likely not delivered in order — they are sent by parallel workers with retry delays, so a
refundcan arrive before asale-completethat 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]
200 within 5 seconds.payme_signature (sale callbacks).notify_type + payme_sale_id + payme_transaction_id.sale_status and sale_price with POST /get-sales.get-sales, because callbacks can be dropped after 3 attempts.