Quickstart
Get a working checkout integration in 5 minutes.
This is the Checkout quickstart — the simplest path. For other integration paths see Choose your integration, or jump straight to the Embedded Fields quickstart if you want buyers to stay on your domain.
Step 0: Get your test keys
Start at vonpay.com/developers — the developer-first landing page. The Get sandbox keys button deep-links into the developer dashboard, where one click on Activate VORA Sandbox mints all three test credentials in seconds:
vp_sk_test_*— secret API key (server-only)vp_pk_test_*— publishable keyss_test_*— session signing secret (verifies return URL signatures)
OTP sign-in (any email), no merchant application, no ops approval, no shared demo credentials.
Your test keys (vp_sk_test_* / vp_pk_test_*) run against Von Payments' built-in sandbox, a mock payment provider. No real card processor is contacted and no money moves — outcomes are deterministic, driven by the test card number (and amount). You can build and test the entire flow this way without boarding a processor. See Sandbox for the outcome table. (Advanced: you can later attach a real-processor sandbox to your account for richer decline testing — still no real funds.)
Already signed in? Skip the landing page and go straight to app.vonpay.com/dashboard/developers.
The same path works whether you're a merchant going live with Von Payments or a developer/platform integrator evaluating VORA — the Activate VORA Sandbox flow short-circuits business-details collection so you can build without going through KYC. The merchant record is real, but the UX never asks you to be a business. See Platform Integrator Sandbox if you're building a connector, or Go-Live Checklist for the path to live keys (vp_sk_live_*) after KYC + contract review.
Other prerequisites
- Node.js 20+ (or Python 3.9+ for the Python SDK)
Step 1: Install the SDK
Node.js
npm install @vonpay/checkout-node
Python
pip install vonpay-checkout
CLI
npm install -g @vonpay/checkout-cli
vonpay checkout login
Pinning to an exact version is recommended during the pre-1.0 window.
Step 2: Create a checkout session
When to create it: mint the session when the buyer clicks Checkout — not on every pageview. It's a server-to-server call with a 30-minute TTL, so creating it earlier just risks it expiring unpaid. See When to create the session.
Node.js
import { VonPayCheckout } from "@vonpay/checkout-node";
const apiKey = process.env.VON_PAY_SECRET_KEY;
if (!apiKey) throw new Error("VON_PAY_SECRET_KEY is required");
const vonpay = new VonPayCheckout(apiKey);
const session = await vonpay.sessions.create({
amount: 1499, // $14.99 in cents
currency: "USD",
successUrl: "https://mystore.com/order/123/confirm",
cancelUrl: "https://mystore.com/cart",
lineItems: [
{ name: "Premium Widget", quantity: 1, unitAmount: 1499 },
],
buyerId: "user_8f3a2b", // your STABLE per-user ID — same value every visit, never randomize
buyerName: "Jane Doe",
buyerEmail: "jane@example.com", // enables cross-session buyer dedup
});
Python
from vonpay.checkout import VonPayCheckout, LineItem
vonpay = VonPayCheckout(os.environ["VON_PAY_SECRET_KEY"])
session = vonpay.sessions.create(
amount=1499,
currency="USD",
success_url="https://mystore.com/order/123/confirm",
cancel_url="https://mystore.com/cart",
line_items=[
LineItem(name="Premium Widget", quantity=1, unit_amount=1499),
],
buyer_name="Jane Doe",
buyer_email="jane@example.com",
)
CLI
vonpay checkout sessions create --amount 1499 --currency USD
cURL
curl -X POST https://checkout.vonpay.com/v1/sessions \
-H "Authorization: Bearer vp_sk_test_xxx" \
-H "Content-Type: application/json" \
-H "Von-Pay-Version: 2026-04-14" \
-H "Idempotency-Key: order_123_attempt_1" \
-d '{
"amount": 1499,
"currency": "USD",
"successUrl": "https://mystore.com/order/123/confirm",
"lineItems": [{"name": "Premium Widget", "quantity": 1, "unitAmount": 1499}]
}'
Response:
{
"id": "vp_cs_test_k7x9m2n4p3",
"checkoutUrl": "https://checkout.vonpay.com/checkout?session=vp_cs_test_k7x9m2n4p3",
"expiresAt": "2026-03-31T15:30:00.000Z"
}
Step 3: Redirect the buyer
Send the buyer to the checkout URL returned in the session response.
Node.js (Express)
res.redirect(session.checkoutUrl);
Python (Flask)
return redirect(session.checkout_url)
The buyer sees the Von Payments hosted checkout page with billing address, payment methods (cards, Apple Pay, Google Pay, Klarna, etc.), and your order summary. You don't need to handle anything on this page.
Step 4: Handle the webhook
When a payment event occurs (charge succeeds, fails, or is refunded), Von Payments sends a signed POST to each registered webhook endpoint that subscribes to that event type. Register an endpoint at /dashboard/developers/webhooks → Webhooks; mint a whsec_* signing secret at create time and store it in your handler env.
Node.js (Express)
import { VonPayCheckout } from "@vonpay/checkout-node";
const apiKey = process.env.VON_PAY_SECRET_KEY;
if (!apiKey) throw new Error("VON_PAY_SECRET_KEY is required");
const vonpay = new VonPayCheckout(apiKey);
const endpointSecret = process.env.VON_PAY_WEBHOOK_SECRET; // whsec_*
app.post("/webhooks/vonpay", express.raw({ type: "application/json" }), (req, res) => {
try {
const event = vonpay.webhooks.constructEvent(
req.body, // raw body (Buffer)
req.headers["x-vonpay-signature"] as string, // signature header
endpointSecret, // whsec_* — per-endpoint secret
);
switch (event.type) {
case "charge.succeeded":
console.log(`Payment succeeded: ${event.data.transaction_id}`);
// fulfill the order
break;
case "charge.failed":
console.log(`Payment failed: ${event.data.failure_reason}`);
break;
}
res.status(200).json({ received: true });
} catch (err) {
console.error("Webhook verification failed:", err.message);
res.status(400).json({ error: "Invalid signature" });
}
});
Python (Flask)
from vonpay.checkout import VonPayCheckout
vonpay = VonPayCheckout(os.environ["VON_PAY_SECRET_KEY"])
endpoint_secret = os.environ["VON_PAY_WEBHOOK_SECRET"] # whsec_*
@app.route("/webhooks/vonpay", methods=["POST"])
def webhook():
try:
event = vonpay.webhooks.construct_event(
request.data,
request.headers.get("x-vonpay-signature"),
endpoint_secret,
)
if event.type == "charge.succeeded":
print(f"Payment succeeded: {event.data['transaction_id']}")
return {"received": True}, 200
except Exception as e:
return {"error": str(e)}, 400
Key details:
- The webhook secret is per-endpoint (
whsec_*), minted when you register the endpoint. Each endpoint has its own. Not the same as your API key. - The
x-vonpay-signatureheader carriest=<unix_seconds>,v1=<hex_hmac>. During a secret rotation window a secondv1=is present; accept on any match. - Replay window is asymmetric: reject if more than 5 min in the past or 30 sec in the future.
- Register
express.raw()for this route before any app-wideexpress.json().constructEventverifies the signature against the raw request body; ifexpress.json()parses it first, verification fails. Scope the raw parser to the webhook path only (as shown above), not globally. - Testing this handler:
vonpay checkout triggercan firesession.*,payment_intent.*, andcharge.refunded— but notcharge.succeeded. To exercise thecharge.succeededbranch specifically, run a real test-mode checkout — it firescharge.succeededon settlement. (The CLI smoke-tests yourpayment_intent.*andcharge.refundedbranches directly; it just can't stand in forcharge.succeeded.) - See Webhooks for the full surface walkthrough and Webhook Verification for the algorithm + reference verifiers in 5 languages.
Step 5: Verify the return redirect
After payment, the buyer is redirected to your successUrl with signed query parameters:
https://mystore.com/order/123/confirm
?session=vp_cs_test_k7x9m2n4p3
&status=succeeded
&amount=1499
¤cy=USD
&transaction_id=abc123
&sig=v2.eyJzaWQiOi...
The example above shows a v2 signature (sig=v2.…), the replay-safe format. Today the server's default is v1 — a bare hex HMAC (sig=<hex>, no v2. prefix). You don't choose: the SDK auto-detects the format and verifies either one, and the expectedSuccessUrl / expectedKeyMode options below bind only on v2 (they're ignored on v1). See Handle the return for the v1 replay caveat.
Always verify the signature server-side. The secret for return signatures is your session secret (VON_PAY_SESSION_SECRET, prefixed ss_test_* or ss_live_*), not your API key.
import { VonPayCheckout } from "@vonpay/checkout-node";
const url = new URL(req.url, `https://${req.headers.host}`);
const isValid = VonPayCheckout.verifyReturnSignature(
{
session: url.searchParams.get("session"),
status: url.searchParams.get("status"),
amount: url.searchParams.get("amount"),
currency: url.searchParams.get("currency"),
transaction_id: url.searchParams.get("transaction_id"),
sig: url.searchParams.get("sig"),
},
process.env.VON_PAY_SESSION_SECRET, // ss_test_* or ss_live_*
{
expectedSuccessUrl: "https://mystore.com/order/123/confirm",
expectedKeyMode: "test", // "live" in production
},
);
if (!isValid) {
throw new Error("Invalid return signature");
}
// Safe to show order confirmation
The SDK auto-detects v1 (the current default) vs v2 signature format. expectedSuccessUrl and expectedKeyMode are required for v2 — see Handle the return for details.
Step 6: Go live
Replace your test keys with live keys. That's it.
- Swap
vp_sk_test_xxxforvp_sk_live_xxx - Update your session secret from
ss_test_*toss_live_* - Ensure
successUrluses HTTPS - Test with a small real payment
Next steps
If you're integrating for your own checkout
- Webhooks guide — event types, payloads, and retry behavior
- Error handling — structured errors and retry logic
- Node SDK reference — full API surface
- CLI reference — command-line tools
- Python SDK — Python integration
- Sample apps — clone-and-run reference integrations: Next.js, Express, Flask, pay-by-link
If you're building a platform/CRM connector
- Platforms — Integrate VORA as a payment gateway — full spec for connector authors: API surface, webhook format, idempotency, error catalog, sandbox matrix
- Platform Integrator Sandbox — how the sandbox flow maps to your connector's dev loop
- Webhook verification — reference HMAC verifier code in Node, Python, Go, Ruby, PHP