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.

8. Multi-provider readiness

Skip this section entirely if you run one provider and intend to keep it that way — that is a legitimate position and most of what follows is cost without benefit. It becomes relevant the moment a second provider is taking real money, or the moment you write a conditional in application code that chooses between providers.

  • The routing decision is configuration you can change without a deployment, not a branch in a checkout controller.
  • A retry that moves to a second provider keeps an identity your own system owns — a provider's idempotency key is scoped to that provider and does not travel.
  • Provider health is tracked per provider, over a rolling window, counting technical failures only. A decline is the provider working correctly and must never mark it unhealthy.
  • An unhealthy provider is re-tested after a cooldown rather than being written off permanently by a fifteen-minute incident.
  • Every transaction carries one internal identifier assigned at capture, with the provider's own reference stored alongside rather than instead — how failover is actually built covers why this is load-bearing.
  • You know that stored payment methods do not transfer between providers, and your renewal flow does not silently depend on them doing so.
  • Your second provider has taken a real payment. An account you opened but never used is not a fallback.

9. Measurement

Everything above is correctness. This section is how you find out that something has drifted, which is the failure mode that costs the most over time because nothing alerts on it.

  • Approval rate is reported split by country, by payment method and by provider — not as one blended figure that averages a healthy market with a broken one.
  • First payments and renewals are counted separately. They have different approval rates and different failure modes, and combining them hides the one that costs existing revenue.
  • The mix of decline reasons is tracked over time, not just the total. A shift in the mix is a signal well before the aggregate moves — why payments fail has the taxonomy.
  • Technical failure rate is alerted on separately from declines. It is the only one of the two that is yours to fix.
  • You can state how many cancellations last month began as a failed charge rather than a customer decision.
  • Someone looks at these numbers on a schedule. A dashboard nobody opens is not measurement.

How to use this without stopping everything

A checklist this long invites two bad responses: doing all of it before shipping anything, or filing it and doing none of it. Neither is necessary, because the items are not equally urgent and they do not all become relevant at the same stage.

Sections 3 and 4 are correctness and never wait. Confirming payments server-side, verifying webhook signatures and carrying an idempotency key are wrong to omit on day one, not at scale. Each is a defect that is merely waiting for enough traffic to become an incident, and all three are cheap while there is one code path.

Section 6 becomes urgent the week a signup form is findable, usually faster than teams expect, and typically before any other item on this list.

Sections 5 and 9 arrive with the second provider, and are usually noticed a quarter after they arrived. Section 8 is only relevant once there is a second provider at all — attempting it earlier is building for a problem you do not have.

The practical way through it is a single pass, marking each item yes, no, or not yet relevant, and resisting the urge to fix anything during that pass. What matters is the resulting list of "no" answers in sections 3 and 4, which is almost always short and almost always contains something worth doing this week. The reasoning behind why these jobs exist at all is in what payment infrastructure has to do.

Explore each part of the stack

Each section above has a page behind it that explains the reasoning rather than just the check:

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.

A single pass takes an afternoon if you resist fixing anything while you go. Mark each item yes, no, or not yet relevant, and only then decide what to act on. What matters is the list of no answers in the confirmation and failure sections - it is almost always short and almost always contains something worth doing that week.

Three. Confirm payments server-side rather than trusting a browser redirect, verify every webhook signature, and send an idempotency key derived from the order and attempt. Each of these is wrong to omit at any size, and all three are cheap while there is still one code path. Everything else has a stage at which it becomes relevant.