Skip to main content

Build your integration

A sequence, not a topic list. Every page linked below is worth reading on its own — but read in the order they appear in the sidebar, they assume each other. This page puts them in the order you actually need them, says what breaks if you skip a step, and tells you how to prove each one landed.

Follow it start to finish and you will have taken a real payment, saved a card if you need one, received the outcome reliably, and tested the failure paths before a buyer finds them.

What this page is not

It does not restate the reference. Each step is a short "why, and what to watch for", then a link to the page that covers it properly. If a step already looks familiar, skip it — nothing here is a prerequisite for reading the next thing.


Step 0 — Decide what you are building

Do this before writing anything. The two integration shapes differ in where the card fields live, who handles 3-D Secure, and how much PCI scope you take on. Choosing wrong is a rewrite, not a refactor.

Compare the integration paths — a capability matrix, a decision tree, and a per-path 3-D Secure table.

How to know you're done: you can name which path you're on, and why.


Step 1 — Get keys, and prove they work before you write code

Activate a sandbox from the dashboard. You get a secret key (vp_sk_test_*), a publishable key (vp_pk_test_*) and a webhook signing secret (whsec_*).

Then spend five minutes proving the key works, before any integration code exists. A key problem found now is five minutes; found in Step 3 it looks like a bug in your card form.

# 1. The key authenticates, and tells you what your account can do.
curl https://checkout.vonpay.com/v1/capabilities \
-H "Authorization: Bearer vp_sk_test_YOUR_KEY"

# 2. You can create a session.
curl https://checkout.vonpay.com/v1/sessions \
-H "Authorization: Bearer vp_sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: smoke_test_session" \
-d '{"amount":1499,"currency":"USD","successUrl":"https://example.com/ok","cancelUrl":"https://example.com/no"}'

# 3. You can read it back.
curl https://checkout.vonpay.com/v1/sessions/SESSION_ID \
-H "Authorization: Bearer vp_sk_test_YOUR_KEY"

How to know you're done: three 2xx responses. A 401 on any of them means the key is wrong or mistyped — check you copied the whole value, and that its mode matches the environment you are calling. None of these three calls rejects a key for being publishable: /v1/capabilities reads account metadata that either key type may consult, and session creation accepts both by design. See API key types for which endpoints do require a secret.

Sandbox setup · API keys · Capabilities


Step 2 — Take one payment, end to end

Get a single successful payment working before you add anything else. Not saved cards, not wallets, not 3-D Secure handling — one charge, one outcome.

Pick the quickstart for the path you chose in Step 0:

The trap that costs a day

On embedded fields, the SDK must be pointed at the same environment your keys belong to. A test key with a production apiBaseUrl fails in a way that looks like a broken card form rather than a configuration error.

How to know you're done: one payment reaches succeeded, and you saw it in the dashboard — not just in your own logs.


Step 3 — Receive the outcome somewhere you can trust

This is the step most integrations get wrong, and the failure is silent: orders that were paid for never get fulfilled.

The redirect back to your site is not proof of payment. It is signed, so you know it came from us — but a declined payment is signed exactly as validly as an approved one. Fulfil from the webhook.

Webhooks — subscribe, verify, respond. → Verifying a signature — constant-time compare, 5-minute replay window. → Which events exist.

How to know you're done: you can point at the line of your code that marks an order paid, and it is inside a webhook handler.


Step 4 — Save a card, only if you need one

Skip this entirely if you take one-off payments. If you bill again later — subscriptions, instalments, cards on file — the requirements are strict, ordered, and fail silently weeks later if you miss one.

Recurring and saved cards — the four steps in order, including the consent declaration that cannot be added afterwards.

Consent is write-once

A card saved without the right consent cannot be repaired later. There is no endpoint that adds it after the fact — you have to collect the card again. The first charge works; the renewal is what declines.


Step 5 — Handle what goes wrong

Everything above is the happy path. This is where a production integration is actually made or lost.

Reconciliation — what to do about an event you missed, a request that timed out, a response you never received. → Troubleshooting — every error code, what it means, what to do, and whether it is safe to retry.

How to know you're done: you know what your code does when a charge request times out. "Retry it" is the wrong answer unless you sent an Idempotency-Key.


Step 6 — Test the behaviour, not just the configuration

This is the step nobody does, and it is the one that catches real bugs.

The go-live checklist verifies your setup — keys, HTTPS, secret storage, replay windows. Every item there can pass while your handler still double-charges a buyer on a redelivered event. These cases test what your code does.

Run every one of these in sandbox before you go live.

Payment outcomes

#TestWhy it mattersDone when
1A successful paymentThe happy pathOrder marked paid, exactly once
2A declined paymentMost integrations only ever test successBuyer sees a useful message; no order created
3A 3-D Secure challenge that succeedsThe challenge redirects away from your page and backOrder paid after the return
4A 3-D Secure challenge that failsThe buyer returns to your site having not paidNo order created; buyer can retry
5An expired / insufficient-funds cardThese carry distinct failure codes worth surfacing differentlyYour UI distinguishes "try another card" from "try again"
Which card produces which outcome depends on your sandbox

On the Vonpay sandbox emulator the test cards behave as documented. If your account routes test traffic to a real processor's sandbox instead, those numbers carry no special meaning — the processor decides, and a card listed here as a decline can come back approved.

This has been measured on a live account. If a "decline" card approves, your integration is not broken and neither is the card table — you are on the other route. On hosted checkout you can force a decline by amount instead: set the session amount to 200 minor units.

Webhook handling

#TestWhy it mattersDone when
6Send the same event twiceWe retry on failure, so duplicates are normal, not exceptionalThe order is fulfilled once. This is the single highest-value test on this page
7Send an event with a tampered signatureAn unverified endpoint will accept anything anyone posts to itRejected, nothing written
8Send an event with a timestamp older than 5 minutesReplay protectionRejected
9Make your handler return a 500Ours will retry; yours must survive thatRetry arrives and is handled cleanly
10Take longer than 30 seconds to respondSlow handlers get retried, causing duplicatesHeavy work moved out of the request

Retries and duplicates

#TestWhy it mattersDone when
11Send the same charge twice with the same Idempotency-KeyThis is the retry your own code will do after a timeoutOne charge. The second returns the original result
12Send the same charge with a different keyProves you understand what the key doesTwo charges — which is why you must never vary it on a retry
13Refund the same payment twice with the same keyA retried refund must not pay the buyer twiceOne refund; the replay is flagged rather than issued
Test 12 is the one worth doing deliberately

It is the only test here designed to produce a wrong outcome. Doing it once, on purpose, in sandbox, is how the rule stops being abstract.


Step 7 — Go live

Go-live checklist — keys, HTTPS, secret storage, monitoring, and the account requirements.

One thing to add to it: log the X-Request-Id header from every error response. It is the first thing support will ask for, and it turns a day of back-and-forth into one message.


The short list of things not to do

Every one of these is covered in depth on the page linked beside it. Collected here because they are the mistakes that actually get made, and because they are easier to avoid than to find afterwards.

Don'tWhyWhere
Fulfil an order from the redirectA decline is signed just as validly as an approvalHandle the return
Treat a pending result as a failureRe-issuing on it charges or refunds twicePayment intents
Mark an order failed on a client timeoutThe charge may have succeeded after your request gave upReconciliation
Add an attempt counter to an Idempotency-KeyRetrying is the case the key exists to collapseIdempotency
Mint a fresh key to get past a 409It removes the only thing preventing a double chargeError codes
Charge again after an embedded charge-at-submitSubmit already took the moneyEmbedded quickstart
Send setup_for_future_use on the charge instead of at collectionConsent is write-once and cannot be repaired laterRecurring and saved cards
Omit return_url when 3-D Secure may triggerThe buyer completes the challenge and lands nowhere3-D Secure
Install a payment processor's own SDK alongside oursIts tokens are not ours, and your page breaks when your account is routed elsewhereIntegration paths
Dedupe webhooks on anything but the event idA session can produce several eventsReconciliation

Where to go next