API: rate limits and idempotency
MeerPartners API rate limiting and idempotency: postback and sign-in limits, the Retry-After header, Idempotency-Key and postback deduplication by external_order_id.
To make the integration resilient, the API limits the request rate and guarantees safe retries. This page is about both mechanisms: what happens when a limit is exceeded and how to retry requests without creating duplicates.
Rate limiting
Limits are counted in a fixed window (a counter in Redis). The limit key depends on the endpoint:
| Endpoint | Limit | Key | Code on exceeding |
|---|---|---|---|
POST /api/v1/postback | ~600 / min | per tenant_api_key (key_id) | POSTBACK_RATE_LIMITED (429) |
POST /api/v1/auth/meerid/exchange | 20 / min | per IP | RATE_LIMIT_EXCEEDED (429) |
GET /r/{code}, GET /p/{code}.gif | anti-flood | per IP hash | TRACKING_RATE_LIMITED (429) |
Exact values are up to the operator
The numbers above are the defaults (postback — 600 requests per minute per key, sign-in — 20 per minute per IP). The platform operator can change them. Plan for exponential backoff, not fixed pauses.
The Retry-After header
On a 429 response, the platform tells you when you can retry via the Retry-After header (seconds), and the body contains the limit and the window:
{
"error": {
"code": "POSTBACK_RATE_LIMITED",
"message": "Too many postbacks",
"details": { "limit": 600, "window_seconds": 60 }
}
}HTTP/1.1 429 Too Many Requests
Retry-After: 23How to handle 429
Don't hammer the endpoint in a loop. On a 429, wait Retry-After seconds (or use exponential backoff with jitter) and retry. For the postback, a retry is safe — it is idempotent by external_order_id (see below).
Postback is protected regardless of limits
Even under a load spike, postback intake is protected by the HMAC signature and the timestamp check, and idempotency by external_order_id prevents double-counting on retries.
Idempotency
Idempotency-Key
On mutating POSTs that create a resource or move money (/affiliate/links, /payouts, /merchant/offers, key issuance and payment operations), pass the header:
Idempotency-Key: 3f8a1c2e-7b3d-4c1a-9f2e-1a2b3c4d5e6fIdempotency-KeystringoptionalBehavior on a repeat:
- Same key + same body → the saved response is returned (the same
2xx) with the headerIdempotent-Replay: true. The side effect (creation, debit) is executed exactly once. - Use the same key on retries due to a network timeout — that way you won't create a second offer/a second withdrawal request.
Postback deduplication
The postback has its own, stronger idempotency — by external_order_id, so Idempotency-Key is secondary for it and not required.
- The deduplication key is the business's
external_order_id(combined with the tenant and the event type). - A repeated postback with the same
external_order_iddoes not create a second conversion: the response is200with"duplicate": trueand the sameconversion_id. - Concurrent simultaneous postbacks are serialized by an advisory lock — a race does not lead to a duplicate.
- The dedup record is stored permanently; for practical retries, rely on a 24-hour window — within it, a repeat is guaranteed to return the original result.
{ "accepted": true, "duplicate": true, "conversion_id": 778 }Uniqueness of external_order_id
external_order_id must be unique on the business side for each order. If you reuse it for different conversions, the second event will be treated as a duplicate and will not be counted.
Signature anti-replay (S2S)
For signed S2S requests (postback, token-exchange), there is additional protection against replaying an intercepted request:
X-Timestampmust be within ±300 seconds of the server time, otherwisePOSTBACK_STALE_TIMESTAMP(400).- The
(key_id, signature)pair is marked as used in Redis for 600 seconds; a repeat of the same signature →POSTBACK_REPLAY(409).
This means: sign each S2S request anew with a current X-Timestamp, and do retries precisely by re-sending the same external_order_id (logical idempotency), not by resending the byte-for-byte same signature. Signature details are on the Postback page.