Merchant API
Public Integration Guide
Canonical docs for creating payments, verifying webhooks, and wiring the hosted checkout into your merchant flow.
API Documentation
Simple integration guide for VoltPay payments.
Payment Simulator
Create test payments with your API key
Webhook Simulator
Test your webhook endpoint
1. Multi-Website Setup
Merchant model: one merchant account can own many websites. API keys and the webhook secret stay shared at the merchant level.
Create or rename websites: use Dashboard → Websites.
Find websiteId: every website card and edit panel on the Websites page shows a copyable websiteId. Use that UUID in payment creation requests.
Configure webhook URLs: when webhook management is enabled for your organization, use Dashboard → Webhooks. Each website keeps its own webhook URL, and all websites share one merchant webhook secret.
Pass websiteId on payment creation: this is the routing key that decides which website owns the payment, which webhook URL receives updates, and how dashboard filtering works.
Compatibility note: older API-key integrations without websiteId still run in temporary compatibility mode. The backend first tries a host-based website match and only then uses a logged legacy fallback. New integrations should always send websiteId.
2. Create Payment
Headers
Authorization: Bearer YOUR_API_KEY Content-Type: application/json
Request Body
{
"websiteId": "550e8400-e29b-41d4-a716-446655440001", // string (UUID), recommended for all integrations and required for deterministic multi-website routing
"amount": 100, // number, required, 1.00-1000.00 USD, up to 2 decimals
"customerEmail": "[email protected]", // string, required, valid email
"description": "Order #123", // string, optional, max 500 chars
"customData": { "orderId": "123" }, // object, optional, max serialized size 5 KB
"paymentType": "ONE_TIME", // string, optional: ONE_TIME | RECURRING
"intervalDays": 30 // number, required when paymentType=RECURRING
}Response
{
"data": {
"paymentId": "550e8400-e29b-41d4-a716-446655440000", // string (UUID)
"websiteId": "550e8400-e29b-41d4-a716-446655440001", // string (UUID)
"amount": 100, // number
"currency": "USD", // string
"status": "CREATED", // string
"description": "Order #123", // string | null
"customerEmail": "[email protected]", // string | null
"customData": { "orderId": "123" }, // object | null
"paymentType": "ONE_TIME", // string
"intervalDays": null, // number | null
"redirectUrl": "https://api.voltpay.pro/r/AbCdEf12", // string
"expiresAt": "2024-01-15T12:00:00Z" // string (ISO 8601)
}
}Checkout redirect: redirect the customer to redirectUrl to continue payment.
Website routing: when you send websiteId, the payment is attached to that website and all later status webhooks are delivered to that website's configured webhook URL.
Billing type behavior (important):
Sending "paymentType": "RECURRING" is only a request, and if checkout falls back to one-time because the selected provider does not support recurring payments, the webhook paymentType is the final source of truth.
3. Receive Webhooks
When webhook management is enabled for your organization, configure each website webhook URL in Dashboard → Webhooks. We'll send a POST request when payment status changes.
Delivery model: webhook deliveries can be retried, so make your handler idempotent.
Deduplication: persist X-Webhook-Id and ignore duplicates.
Ordering: process events by occurredAt and payment state, not by arrival order.
Recurring fallback: use webhook paymentType as the final answer. A requested recurring payment can finish as one-time if the provider used for checkout does not support recurring billing.
Shared secret: all website webhooks are signed with the same merchant webhook secret. URLs are website-specific; signature verification is merchant-scoped.
Webhook Headers
X-Webhook-Signature: sha256=<hex HMAC-SHA256 of raw body> X-Webhook-Timestamp: <milliseconds since epoch> X-Webhook-Id: <unique delivery id>
Webhook Body
{
"eventType": "payment.status.changed", // string
"paymentId": "550e8400-e29b-41d4-a716-446655440000", // string (UUID)
"websiteId": "550e8400-e29b-41d4-a716-446655440001", // string (UUID)
"websiteName": "Main Store", // string
"oldStatus": "PENDING", // string
"newStatus": "PAID", // string: CREATED | PENDING | PROCESSING | PAID | FAILED | CANCELLED
"occurredAt": "2024-01-15T12:05:00Z", // string (ISO 8601)
"customerEmail": "[email protected]", // string
"customData": { "orderId": "123" }, // object | null
"paymentType": "ONE_TIME", // string: ONE_TIME | RECURRING
"intervalDays": null // number | null
}Verify Signature (Node.js)
import crypto from "crypto";
function verifyWebhook(body, signature, timestamp, secret) {
const signedPayload = `${timestamp}.${body}`;
const expected = crypto
.createHmac("sha256", secret)
.update(signedPayload, "utf8")
.digest("hex");
const received = signature.replace(/^sha256=/i, "");
return crypto.timingSafeEqual(
Buffer.from(received, "hex"),
Buffer.from(expected, "hex")
);
}4. Payment Statuses
Need help? Contact support or check the Payment Simulator to test your integration.