Quick start
Authenticate, identify your key, and create your first payment intent in five minutes.
One REST surface for payment intents, payout intents, and balances. Idempotent by design. Live and test are isolated by API key. This five-minute walkthrough ends with a test API key pair, a verified identity call, and a payment intent you can present to a customer.
Everything here runs in test. Test keys route to test PSP credentials and never move real money, so you can run the whole walkthrough against production base URLs without risk.
Base URLs
Live and Test share one base URL — the API key prefix selects the environment, so there is no separate sandbox host. See Environments.
| Base URL | Use for |
|---|---|
https://api.voltzpay.co | Production |
http://localhost:18320 | Local development |
Walkthrough
Get a test API key pair
Sign in to the dashboard, open your merchant, and create an API key in the test environment. The dashboard issues a public/secret pair: the public key is prefixed pk_test_ and the secret key is prefixed sk_test_.
export API_PUBLIC_KEY="pk_test_..."
export API_SECRET_KEY="sk_test_..."
export API_BASE="https://api.voltzpay.co"The secret key is shown once at creation. Store it in a secret manager — never in a frontend bundle or a committed file.
Verify the key pair
curl -s "$API_BASE/me" \
-u "$API_PUBLIC_KEY:$API_SECRET_KEY"{
"environment": "test",
"merchant_id": "mch_01H...",
"organization_id": "org_01H..."
}environment echoes the stamp on the key. If it reads test, the key pair is wired correctly.
Create a payment intent
curl -s -X POST "$API_BASE/payment_intents" \
-u "$API_PUBLIC_KEY:$API_SECRET_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": "10000",
"currency": "MYR",
"country": "MY",
"payment_method": "MY_FPX",
"merchant_reference": "order_12345",
"customer": {
"name": "Aisyah Rahman",
"email": "[email protected]"
}
}'import { randomUUID } from "node:crypto";
const auth =
"Basic " +
Buffer.from(`${process.env.API_PUBLIC_KEY}:${process.env.API_SECRET_KEY}`).toString("base64");
const res = await fetch(`${process.env.API_BASE}/payment_intents`, {
method: "POST",
headers: {
Authorization: auth,
"Idempotency-Key": randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: "10000",
currency: "MYR",
country: "MY",
payment_method: "MY_FPX",
merchant_reference: "order_12345",
customer: { name: "Aisyah Rahman", email: "[email protected]" },
}),
});
const intent = await res.json();The 201 response carries the intent id, status, and next_action when the provider returns an action in time. amount is "10000" for MYR 100.00 — a minor-unit string, never a number. See Currency.
Poll for status
curl -s "$API_BASE/payment_intents/dord_..." \
-u "$API_PUBLIC_KEY:$API_SECRET_KEY"Statuses are processing, requires_action, succeeded, failed, and expired. For production integrations, subscribe to webhooks instead of polling.