Important
Novupay does not expose /api/card2pay/start on portal.novupay.com. That path would live on your store if you want it — your backend builds the encrypted hash and redirects the customer to Novupay.
Flow
- Customer clicks Pay on your site.
- Your server creates a JSON payload (order id, amount, currency, email, callback URL…).
- Your server encrypts it into a hash using your API secret.
- Browser redirects to
https://portal.novupay.com/pay/h/<hash> - Novupay decrypts the hash and immediately redirects the browser to InfinitePay.
- When paid, Novupay POSTs
order.paidto your callback URL.
Credentials
Create a site in Dashboard → Sites (API):
api_key— starts withc2p_api_secret— 64 hex chars, used for encryption + callback signature verification
Payload JSON (before encryption)
Amount and currency go inside this JSON — not as separate query params on a Novupay API URL.
{
"order_id": "12345",
"amount": 240.00,
"currency": "USD",
"email": "customer@example.com",
"description": "Order 12345",
"return_url": "https://your-store.com/order/12345/thank-you",
"callback_url": "https://your-store.com/api/card2pay/webhook",
"ts": 1710000000000
}| Field | Required | Notes |
|---|---|---|
| order_id | Yes | Your order id (string) |
| amount | Yes | Number, e.g. 240.00 |
| currency | Yes | ISO code: USD, EUR, … |
| Strongly recommended | Required for card checkout (PayGate) | |
| callback_url | Recommended | Where we POST when paid (can also set on site in dashboard) |
| return_url | Optional | Redirect customer back after payment |
Hash format
c2p1.<api_key>.<iv_b64url>.<ciphertext_b64url>.<tag_b64url>
- AES-256-GCM
- Key = SHA-256(api_secret) as raw 32 bytes
- IV = 12 random bytes
- AAD (additional authenticated data) = api_key string
- Plaintext = JSON payload UTF-8
Checkout URL (what the customer opens)
https://portal.novupay.com/pay/h/c2p1.c2p_abc123....
HTTP GET redirect (303). Novupay creates the order and sends the browser straight to InfinitePay. The store never calls InfinitePay and never handles its API key.
Example: your /api/card2pay/start (Node.js)
This runs on your server, not on Novupay:
import crypto from "node:crypto";
const API_KEY = process.env.CARD2PAY_API_KEY;
const API_SECRET = process.env.CARD2PAY_API_SECRET;
const CARD2PAY_URL = "https://portal.novupay.com";
function b64url(buf) {
return buf.toString("base64url");
}
function encodeHash(payload) {
const key = crypto.createHash("sha256").update(API_SECRET, "utf8").digest();
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
cipher.setAAD(Buffer.from(API_KEY, "utf8"));
const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
const tag = cipher.getAuthTag();
return ["c2p1", API_KEY, b64url(iv), b64url(ciphertext), b64url(tag)].join(".");
}
// POST /api/card2pay/start { orderId, amount, currency, email }
export function startCheckout(req, res) {
const payload = {
order_id: String(req.body.orderId),
amount: Number(req.body.amount),
currency: String(req.body.currency || "USD").toUpperCase(),
email: req.body.email,
callback_url: "https://your-store.com/api/card2pay/webhook",
return_url: `https://your-store.com/orders/${req.body.orderId}`,
ts: Date.now(),
};
const hash = encodeHash(payload);
res.redirect(302, `${CARD2PAY_URL}/pay/h/${hash}`);
}Example: PHP (same as WooCommerce plugin)
$payload = [
'order_id' => (string) $orderId,
'amount' => 240.00,
'currency' => 'USD',
'email' => 'customer@example.com',
'callback_url' => 'https://your-store.com/api/card2pay/webhook',
'return_url' => 'https://your-store.com/thank-you',
'ts' => round(microtime(true) * 1000),
];
$key = hash('sha256', $apiSecret, true);
$iv = random_bytes(12);
$tag = '';
$ciphertext = openssl_encrypt(
json_encode($payload),
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$iv,
$tag,
$apiKey,
16
);
$b64url = fn($bin) => rtrim(strtr(base64_encode($bin), '+/', '-_'), '=');
$hash = implode('.', ['c2p1', $apiKey, $b64url($iv), $b64url($ciphertext), $b64url($tag)]);
header('Location: https://portal.novupay.com/pay/h/' . $hash, true, 302);
exit;When payment is confirmed
Novupay POSTs JSON to your callback_url. Verify header x-card2pay-signature: sha256=… with HMAC-SHA256 of the raw body using your api_secret.
Test without coding
In Dashboard → Sites (API), use Test checkout hash — it builds a valid hash with your amount and opens checkout.
Common mistakes
- Calling
portal.novupay.com/api/…/start— does not exist - Putting amount/currency in query string instead of encrypted JSON
- Building PayGate
pay.phpURLs yourself — Novupay does that after hash decode - Missing
email— card checkout returns 400 - Wrong api_secret → hash decrypt fails → pay/error