Guide

Best practices

Webhook dedup, server-side confirmation, 429 handling, monitoring and a security checklist.

Deduplicate webhooks

A webhook may be delivered more than once (we can\u2019t tell whether your server answered after 15 seconds or just failed). The event\u2019s id is stable across retries. Keep at least the last 24 hours of processed ids and ignore duplicates.

// Postgres approach: unique key on event.id, ON CONFLICT DO NOTHING.
INSERT INTO nerezpay_events (event_id, kind, payload, created_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (event_id) DO NOTHING;

// If SQL affected 0 rows — it’s a duplicate, skip side-effects.

Confirm payment server-side, not client-side

After payment we return the customer to return_url with ?payment_id=…&status=succeeded. Any user can rewrite these parameters by hand. Before celebrating a purchase, confirm the status via webhook or an explicit GET /payments/{id} check with status === 'succeeded'.

One Idempotency-Key per business operation

Don\u2019t generate a fresh key on every retry — that creates a duplicate payment. Tie the key to a business entity: e.g. order:{id}:create to create a payment, order:{id}:refund:50000 to refund a specific amount. Same key + same body → 200 OK with the existing object.

Don’t store card data

We never expose card numbers, CVV or expiry — only masks (customer_card_mask: 411111******1111) and brand. PCI DSS requires it. Don\u2019t try to parse a full PAN out of the response — it isn\u2019t there and never will be.

Handle 429 — it’s normal

The merchant\u2019s public-API limit is configurable from the NerezPay admin panel. On overflow we return 429 rate_limited with Retry-After in seconds. Don\u2019t ignore the header: retrying sooner won\u2019t help. All our SDKs throw NerezpayError with code: 'rate_limited' — catch and back off.

Test mode is test, not a production-debug shortcut

In test mode payments hit the simulator. Don\u2019t assume "it works on test, it works on live" — the live acquiring bank adds real cases: 3DS failures, insufficient funds, blocked cards, fraud blocks. Run the cases from the Sandbox section — those are scenarios that actually happen in live.

Monitor the key metrics

  • Share of payment.failed by failure_code. A spike in card_declined is a reason to switch to a backup connector.
  • Time from payment.created to payment.succeeded. P95 > 30 s — something\u2019s wrong with the bank.
  • Share of 429 rate_limited responses. If >0% — time to ask NerezPay admins for a higher limit.
  • Age of the last successful webhook. If nothing arrived in the last hour but payments are happening — check the webhook endpoint and retry logic in the dashboard.

Security checklist

  • API keys live in CI/CD secrets or a secret manager. Never in git and never in a front-end bundle.
  • Webhook endpoint — HTTPS only in production. We’ll accept HTTP (for localhost), but your data goes in plaintext.
  • Enable request HMAC signing (the "Require signature" flag on the shop) — a second factor on top of the Bearer key.
  • IP allow-list is mandatory for live keys. List your backend’s IPs and nothing else.
  • Rotate the webhook secret every six months (Rotate button in the dashboard). The old one stops working immediately; the new one is shown once.
  • Log X-PSP-Event-Id for every accepted webhook — gives you correlation with our delivery log.