Callbacks (Partner)
Reference page: Callbacks has the signature formula, the payload fields and the worked example. This page holds what you must do with them.
What each callback gets
notify_type |
Signed? | What to do |
|---|---|---|
sale-complete, sale-authorized, refund, sale-failure, sale-chargeback, sale-chargeback-refund |
Yes, or null when no transaction exists | Check the signature, then confirm with get-transactions |
sub-* |
Never | Re-read with get-subscriptions: see Subscriptions |
seller-create, seller-update, seller-approve |
Never | Ignore the body, re-read with get-sellers |
withdrawal-complete |
Never | Ignore the body, re-read with get-withdrawals: see Balances and Payouts |
A sale callback, in order
Parse the form-encoded body. Record the event.
Check
payme_signaturewith a constant-time comparison.Result Action Valid Worth checking: go on to step 3 Wrong, or no secret configured Record it, change nothing, alert on runs Null or empty No transaction behind it. Record it, and move no money Find the order by the
payme_sale_idyou stored (CB-05). An unknown ID stops here, with no query, so nobody can make you spend API calls.Query
get-transactionswith the seller's MPL in the JSON body and your order ID astransaction_id. To confirmsale-authorized, addtransaction_type: 40: a default query skipped an uncaptured authorization in the sandbox and returned no rows.Apply the row checks, then apply PayMe's status and nothing from the callback.
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.
A signature covers the credentials and two IDs, not the status, the amount or the notification type. A replayed callback with its type or price changed still passes, which is why step 4 is not optional.
The transaction_id trap
On a sale callback, transaction_id is your order ID and PayMe's ID is in payme_transaction_id. On a subscription return URL, transaction_id is PayMe's. Hashing the wrong one looks exactly like a wrong secret. Keep two separate functions, as reference/partner-signature.ts does.
Checking the rows
- If any row names another seller, discard the whole answer and alert.
- Keep the rows for your stored sale ID. Each must agree with your order on order ID, seller and currency where it carries them.
- For
completedorauthorized, at least one row must havetransaction_priceequal to the order amount. - The sale's status is the
sale_statusof the newest row bytransaction_created_at.
Partial capture fails this check, because no row matches the full order amount. That failure is safe. Don't work around it: see Capture, Refund, Void.
Keep your status moving forward
PayMe publishes no transition table, so use your own conservative one:
| From | May move to |
|---|---|
initial |
authorized, completed, failed, canceled |
failed |
failed, authorized, completed |
authorized |
completed, voided, partial-void, failed |
completed |
partial-refund, refunded, chargeback, partial-chargeback |
partial-refund |
partial-refund, refunded, chargeback, partial-chargeback |
For anything else, record it and don't guess. A sale fully refunded in parts stays partial-refund, so track the total yourself.
failed isn't final. A buyer can retry on the same page after a decline, and the sale then moves to completed, or authorized for a capture sale. If your table has no way out of failed, a paid order is never fulfilled. Reuse the same hosted sale (Checkout) and keep re-reading it. A failed capture-sale also leaves the sale failed, and a later capture can still complete it.
Delivery
- Any status below 400 counts as delivered. A 4xx is retried, as is a 5xx or a timeout, up to three attempts in all.
- A retry can't fix a bad signature, which is why you answer below 400 when rejecting.
- Repeated failures can make PayMe skip callbacks to your domain for a while. Those are not delivered, so reconciling is required.
- Seller and withdrawal callbacks go only to your Default Callback URL. Keep one handler there that routes by
notify_type. - Keep at most one seller re-read in flight per seller, so a flood of unsigned callbacks can't become a flood of API calls.
Reconcile
On a schedule, re-read sales still initial, failed or authorized, any sale a callback mentioned that you couldn't record, and authorizations near their limit. A reasonable schedule is every few minutes for the first hour, then hourly for a day.
Finished orders need it too. Callbacks can be skipped, so a chargeback or a refund made in the dashboard may only show up on a re-read. PayMe publishes no window for these, so this schedule is a suggestion: once a day, for each seller, query get-transactions with transaction_status: ["refunded", "p-refund", "chargeback", "partial-chargeback", "chargeback-refund", "partial-chargeback-refund"] and transaction_created_min set seven days back, so a missed run is caught by the next. Match rows to your orders by sale ID, and count each row once by payme_transaction_id (reading the rows). Reporting Across Your Sellers and Reconcile Sales and Withdrawals cover the queries.
Test it
reference/partner-callbacks.ts, reference/partner-signature.ts and their test cases in the integration kit pin this page. Send a forged callback through your public address 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=SALE-X&payme_transaction_id=TRAN-X&transaction_id=order-1&price=5075¤cy=ILS&payme_signature=00000000000000000000000000000000'
Expect a status below 400, the event recorded as rejected, and the order unchanged.
With no public address
The return-URL redirect uses the same signature, so checking a real one proves your partner secret and your formula. Posting that payload to your own handler proves the handler. Neither proves delivery: PayMe posting a real callback to you still needs a public address. Report that as not proven.