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 secret, issued alongside them; your integration does not need it
Your webhook signing secret (whsec_*) is separate again — one is issued per webhook endpoint when you create it.
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: Confirm the payment when the buyer returns
After payment, the buyer is redirected to your successUrl with the session ID:
https://mystore.com/order/123/confirm?session=vp_cs_test_k7x9m2n4p3
That redirect means the buyer came back. It does not mean they paid. Read the outcome from the API with your secret key:
import { VonPayCheckout } from "@vonpay/checkout-node";
const client = new VonPayCheckout(process.env.VON_PAY_SECRET_KEY);
const sessionId = new URL(req.url, `https://${req.headers.host}`)
.searchParams.get("session");
const { status } = await client.sessions.get(sessionId);
if (status === "succeeded") {
// Show the confirmation page
} else if (status === "pending" || status === "processing") {
// Still in flight — show "confirming your payment", NOT a failure
} else {
// failed or expired — offer to try again
}
A buyer who pays and then closes their laptop never loads this page. Fulfilment belongs on the charge.succeeded webhook, which reaches you regardless of what the browser does. This page is for showing the buyer something immediately.
And treat pending / processing as "still working", never as a failure — on the 3-D Secure path buyers routinely arrive here before the payment settles, and a failure page makes them pay twice.
See Handle the return for the full pattern, including fulfilling exactly once.
Step 6: Go live
Replace your test keys with live keys. That's it.
- Swap
vp_sk_test_xxxforvp_sk_live_xxx - Point your webhook subscription at your production endpoint and use its
whsec_*secret - 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