Idempotency and Retries

"Idempotent" means that sending the same request twice has the same effect as sending it once. PayMe has no idempotency key, so you must make retries safe yourself.

What happens when a request runs twice

Request Second call while the first is still running Second call after the first finished
generate-sale A second, separate sale A second, separate sale — transaction_id is not checked for uniqueness
generate-sale with buyer_key / token (creates and charges) A second charge is possible A second charge is possible
pay-sale Rejected: HTTP 429, code 998 "Duplicated action detected, please retry later" If the first succeeded: rejected with HTTP 500, code 305, current status in status_additional_info. If the first failed: accepted as a new charge attempt.
refund-sale — partial Rejected: HTTP 429, code 998 Runs again — a second refund, as long as refundable balance remains
refund-sale — full Rejected: HTTP 429, code 998 Rejected with code 305
capture-sale Rejected: HTTP 429, code 998 An authorization can be captured only once.

Notes:

Before retrying, check the sale

If a request timed out and you do not know whether it ran, look the sale up instead of resending it.

POST /get-sales accepts these filters:

By default get-sales leaves out unpaid sales (status initial). To find an unpaid sale, pass sale_status including initial.

get-sales works with seller_payme_id alone; partners can also use payme_client_key.

How to tell whether a timed-out operation ran: read sale_status from get-sales — a capture shows completed (not authorized or failed), a full refund refunded, a void a voided status. After a partial refund the status is partial-refund, and it stays partial-refund even once partial refunds reach the full amount, so track the refunded total yourself. sale_status alone cannot show whether a retried partial refund already ran: compare sale_refund_buffer (the amount still refundable, returned by refund-sale and sent in each refund callback — get-sales does not return it) or count the refund callbacks, each of which has its own payme_transaction_id.

Retrying safely

  1. Retry only on network errors, timeouts, HTTP 429 code 998, or codes 991, 20002 and 999, whose outcome is unknown. Never retry a response with status_code 1 that names a problem with your input.
  2. On 998, wait (honour the Retry-After header), then check the sale before retrying.
  3. Before retrying anything that moves money (pay-sale, refund-sale, capture-sale, generate-sale with buyer_key), check the sale's current state as above.
  4. Store PayMe's payme_sale_id as soon as generate-sale returns, so every later step refers to one sale.
  5. Treat callbacks as confirmation, and process each one once — see Callbacks.

PayMe's own retries

Rate limits

The default applies to both environments unless PayMe has set a different limit for your partner. To request a higher limit, write to partners@payme.io with the limit you need, whether it is a burst or a permanent need, and your current plan; increases are reviewed by PayMe's technical team. Until then, handle 429 with exponential backoff and request spacing.

Keep's rate limit is documented on Keep → Partner API overview.