Callbacks (Direct Seller)

Reference page: Callbacks has the payload fields. A direct seller has no partner secret, so it can't check payme_signature, and no callback body is ever trusted (CB-01).

A sale callback, in order

  1. Parse the form-encoded body. Record the event.
  2. Read payme_sale_id, and find your order by the ID you stored at generate-sale. No match: record it and change nothing, with no query.
  3. Query get-transactions with your MPL in the JSON body and your order ID as transaction_id. To confirm sale-authorized, add transaction_type: 40: a default query skipped an uncaptured authorization in the sandbox and returned no rows.
  4. If any row names another seller, discard the answer and alert.
  5. Keep the rows for your sale ID. Each must agree with your order on order ID, seller and currency where it carries them. For completed or authorized, at least one row must have transaction_price equal to the order amount.
  6. The status is the sale_status of the newest row by transaction_created_at. Apply that and nothing else.
  7. Answer below 400 (CB-06). PayMe waits 5 seconds, so if the query can take longer, record the event, answer first and confirm in the background.

get-sales also reports a sale's status and price, and the reference pages use it for a quick check. These steps use get-transactions because its rows carry each amount and action, and get-sales leaves out initial sales by default.

Why the query uses your order ID

Asking only "is this sale ID completed?" can be beaten. An attacker buys something cheap, keeps the real sale ID, and sends it in a forged callback for an expensive order. That sale really is completed. Finding the order by the ID you stored, querying by your order ID and matching the amount closes that.

What each callback gets

notify_type What to do
sale-complete, sale-authorized, refund, sale-failure, sale-chargeback, sale-chargeback-refund Confirm with the steps for a sale callback
sub-* Confirm with get-subscriptions: see Subscriptions
seller-*, withdrawal-* Partner notifications. Record and ignore

Handler rules

Re-check on a schedule

Callbacks can be lost, and repeated failures can make PayMe skip your domain. Run the same function on a timer for:

Finished orders need it too, because a chargeback or a dashboard refund may only show up on a re-read. PayMe publishes no window for these, so this schedule is a suggestion: once a day, query get-transactions with your MPL, transaction_status: ["refunded", "p-refund", "chargeback", "partial-chargeback", "chargeback-refund", "partial-chargeback-refund"] and transaction_created_min set seven days back. Count each row once by payme_transaction_id (reading the rows).

Reconcile Sales and Withdrawals and Find the Right Data cover the queries.

Test it

reference/seller-callbacks.ts, reference/sale-query-verification.ts and their test cases in the integration kit pin this page. Send a forged callback for a real, unpaid order at least once:

curl -X POST https://your-host.example/payme/callbacks \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'notify_type=sale-complete&status_code=0&payme_sale_id=YOUR-UNPAID-SALE-ID&transaction_id=YOUR-ORDER-ID&price=5075&currency=ILS'

Expect a status below 400, the event recorded as not confirmed, and the order unchanged.