Pre-Launch

The checks that only matter after you have real customers

A payment integration that works is easy to build. One that behaves correctly when the network does not, when a webhook arrives twice, or when a provider succeeds and forgets to mention it — that is the part discovered in production, usually by a customer. This is the list for whoever wrote the code. If you are checking the store rather than the integration, test your checkout before you go live is the version for that job.

1. Before the first line of code

  • You can name the currencies you will charge in, and have confirmed each provider processes them.
  • You know which payment methods your largest markets actually use, rather than assuming cards.
  • You know whether your business category attracts holds or reserves at your provider.
  • You have decided whether card data will ever touch your server — and if the answer is anything but "no", you have read how PCI scope works first.

2. Creating a charge

  • Every charge carries an idempotency key.
  • The key is derived from the order, not generated per HTTP request — you can reproduce it from data you already store.
  • The key is stored with the order before the request is sent, not after the response comes back.
  • You have tested that sending the same charge twice produces one payment.
  • Amounts are handled in a type that cannot round — no floats anywhere between the cart and the provider.
  • The amount sent matches the amount displayed, in the unit the provider expects. (Major units and minor units are both common, and the mismatch is a factor of one hundred.)

3. Confirming the payment

  • Orders are marked paid from a server-side confirmation, never from the browser redirect.
  • Your success URL does nothing but show a page — no state changes, no fulfilment.
  • Every inbound webhook signature is verified before the payload is trusted.
  • Signature comparison is constant-time.
  • A webhook that fails verification is rejected, not logged and processed anyway.
  • You have confirmed you are verifying against the raw request body — parsing and re-serialising the JSON first breaks the signature for every provider.
  • Webhook handlers are idempotent: the same event delivered twice changes nothing the second time.
  • Handlers tolerate out-of-order delivery and never move an order backwards from a later state.

4. When things fail

  • You distinguish technical failures from hard declines from soft declines, and treat them differently — the three categories are in why payments fail.
  • A hard decline is never retried through another provider.
  • A timeout triggers a status query, not a blind retry.
  • There is a defined maximum time an order may sit in an unknown state before a human sees it.
  • You have deliberately tested the case where the provider charged the card and the response never arrived. This is the single most valuable test on this page.
  • Your failover path — if you have one — carries the original idempotency key. If it does not, it is a double-charge generator; see how failover is actually built.
  • The customer sees a message that tells them what to do next, not a raw provider error code.

5. Money correctness

  • Refunds work end to end, and the money is visible as returned on the provider's side too.
  • Partial refunds and partial captures behave, if you support them.
  • A refund issued at the provider rather than in your system still reaches your order state.
  • Disputes and chargebacks land somewhere a human will look.
  • You can answer "what did we take yesterday" across every provider without a spreadsheet merge — reconciliation across providers is what this looks like when it is missing.

6. Risk and abuse

  • There is a limit on payment attempts per customer in a window.
  • There is a limit on distinct cards from one customer or one address.
  • Repeated provider rejections trigger something automatically rather than waiting for a report — the pattern is described in card testing on signup forms.
  • You know what your provider's decline rate threshold is, and you monitor against it.

7. Operations

  • API keys live in a secret store, not in configuration files or environment dumps.
  • Payment logs contain no card data, no full tokens and no webhook secrets.
  • There is an alert on payment failure rate, not just on server errors — a provider outage often produces perfectly healthy 200s from your own stack.
  • One person other than the author can find the payment logs and explain what a stuck order means.
  • You have done one real payment with live keys, for the smallest amount possible, and refunded it.

How PaymentHood fits

Sections 2 and 3 are the ones teams most often get partly right, and they are the ones PaymentHood handles centrally. Charge creation is idempotent on your own order reference, checked before any provider is contacted. Provider webhook signatures are verified in the layer rather than by each integration. Orders are confirmed server-side against the provider, and the webhooks PaymentHood sends you are themselves signed with HMAC-SHA256 over a timestamped payload, so your own handler can verify them the same way — the scheme and sample verification code are in the API documentation (opens in new tab).

Sections 5 through 7 stay yours, as they should — they are about your business, not your payment layer. What changes is that you do the work once instead of once per provider.

Start from a layer that already passes sections 2 and 3

Create a free account and take a sandbox payment. The idempotency, signature verification and server-side confirmation are already in place.

Checklist FAQ

Frequently Asked Questions

The failure paths, because the success path is the one you have already run a hundred times. In order of how much they cost when wrong: a timeout where the provider succeeded and did not tell you, a webhook arriving twice, a webhook arriving out of order, a declined card, and a refund. If you only have time for one, make it the first.

Do not wait for one. Force it - block the provider's response at the network level after the request leaves, or point the integration at a stub that accepts the charge and then never replies. Then check the only thing that matters: does your system end up with one charge and a correct order state, or two charges?

Yes. A URL is not a credential - it leaks through logs, proxies, browser history and error reports, and it never rotates. Verify the signature on every inbound webhook, compare in constant time, and reject anything that fails rather than logging it and continuing.

No. The browser redirect proves the customer's browser was redirected and nothing else - it is attacker-controllable, and honest customers close the tab before it happens. Mark the order paid when your server has confirmed the capture with the provider, and treat the redirect as a navigation event only.

Deriving the idempotency key from the request instead of from the order. It looks correct, passes every test written against the happy path, and protects nothing - because a retry is a new request and gets a new key. The key has to identify the payment attempt and be reproducible from data you already store.

Working through the list against an existing integration is usually an afternoon, and most teams find two or three genuine gaps. Building the items you are missing is the longer part, which is the argument for checking before launch rather than after the first incident.