Introduction
The Chargedeck Integration API lets any external site - a WooCommerce store, a custom checkout, any platform - spin up a hosted Chargedeck checkout for a dynamic cart total. The buyer pays on a branded Chargedeck page, the order and total land in Chargedeck, and your site is notified with a signed webhook so it can mark its own order paid.
Base URL
https://checkout.2.25.199.135.nip.io
Amounts
Every amount is an integer in minor units, exactly like Stripe: 4999 means £49.99, 2500 means $25.00. The minimum is 30 (the processor floor). Supported currencies: gbp, usd, eur, cad, aud (always lowercase).
Authentication
Every API request authenticates with your secret API key as a Bearer token. Keys look like cdk_live_<hex> and resolve to exactly one seller - a key can only ever read or act on your own data.
Authorization: Bearer cdk_live_4f0b9c2e…
cdk_live_ secret is shown once. Store it server-side.
Create a checkout
Create a hosted checkout for a dynamic cart total. The amount you send here is server-trusted - because it arrives over an authenticated Bearer call, it is frozen server-side and the buyer can never change it.
curl https://checkout.2.25.199.135.nip.io/api/v1/checkouts \
-H "Authorization: Bearer cdk_live_4f0b9c2e…" \
-H "Content-Type: application/json" \
-d '{
"amount": 4999,
"currency": "gbp",
"description": "Order #1042",
"reference": "1042",
"success_url": "https://siteA.com/thank-you?o=1042",
"cancel_url": "https://siteA.com/cart",
"customer_email": "buyer@example.com"
}'
| Field | Type | Notes |
|---|---|---|
amount | integer | Required. Minor units, 30–99999999. Authoritative - line items do not need to sum to it. |
currency | string | Required. One of gbp usd eur cad aud. |
reference | string | Your external order id (≤200 chars). Echoed back in the webhook. |
description | string | Shown on the checkout page. |
success_url | string | Stored for your own routing (see the note in the redirect flow). |
cancel_url | string | Stored for your own routing. |
customer_email | string | Optional pre-fill. |
line_items | array | Optional, for your own records. Never sent to any processor (white-label). |
Response
{
"id": "cs_7hK2p…",
"url": "https://checkout.2.25.199.135.nip.io/p/cs_7hK2p…",
"reference": "1042",
"amount": 4999,
"currency": "gbp",
"status": "open",
"expires_at": 1720986400
}
Redirect the buyer's browser to url. To poll a session's status later, call GET /api/v1/checkouts/:id with the returned id; to list orders, call GET /api/v1/orders?reference=1042.
The redirect flow
The whole handshake is four steps. The buyer never leaves a Chargedeck-hosted page until they are done, and the webhook is the authoritative "paid" signal your site acts on.
POST /api/v1/checkouts and gets back a hosted url./p/cs_… - the branded Chargedeck checkout.order.paid event to your webhook URL. You mark your order paid.success_url. Treat the signed webhook as the only source of truth that an order is paid. success_url and cancel_url are stored for your own routing.
Verify webhooks
When an order becomes paid, Chargedeck POSTs a JSON order.paid event to the webhook URL you set in the dashboard. It is signed with your webhook secret (whsec_<hex>) so you can verify authenticity and reject replays.
Chargedeck-Signature: t=1720900000,v1=4e0b…c7
Content-Type: application/json
User-Agent: Chargedeck-Webhooks/1
{
"id": "evt_9c3f…",
"type": "order.paid",
"created": 1720900000,
"data": {
"order_id": "ord_a1b2…",
"checkout_id": "cs_7hK2p…",
"reference": "1042",
"amount": 4999,
"currency": "gbp",
"status": "paid",
"seller_id": "sel_44f0…",
"paid_at": 1720900000
}
}
How to verify
- Read the raw request body - the exact bytes, before any JSON parse.
- Parse
tandv1from theChargedeck-Signatureheader. - Reject if
tis more than 300 seconds from now (replay window). - Compute
HMAC-SHA256(secret, "<t>.<rawBody>")and compare tov1in constant time. - Only then parse the JSON and mark your order paid (idempotently - the event may arrive more than once).
PHP
$secret = 'whsec_…'; // from your dashboard
$raw = file_get_contents('php://input'); // RAW body, before json_decode
$hdr = $_SERVER['HTTP_CHARGEDECK_SIGNATURE'] ?? '';
// parse "t=…,v1=…"
$t = $v1 = '';
foreach (explode(',', $hdr) as $part) {
[$k, $v] = array_pad(explode('=', $part, 2), 2, '');
if ($k === 't') $t = $v;
if ($k === 'v1') $v1 = $v;
}
// reject replays (300s window)
if (abs(time() - (int)$t) > 300) { http_response_code(400); exit; }
$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
if (!hash_equals($expected, $v1)) { http_response_code(401); exit; }
$event = json_decode($raw, true);
if (($event['type'] ?? '') === 'order.paid') {
$d = $event['data'];
// look up YOUR order by $d['reference']; check amount + currency; mark paid (idempotent)
}
http_response_code(200);
Node.js (Express)
const crypto = require('crypto');
const SECRET = 'whsec_…'; // from your dashboard
// IMPORTANT: capture the RAW body, not the parsed object
app.post('/chargedeck/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const raw = req.body; // Buffer of exact bytes
const hdr = req.headers['chargedeck-signature'] || '';
const parts = Object.fromEntries(
hdr.split(',').map(p => p.split('=')));
const t = parts.t, v1 = parts.v1 || '';
// reject replays (300s window)
if (Math.abs(Date.now()/1000 - Number(t)) > 300)
return res.status(400).end();
const expected = crypto.createHmac('sha256', SECRET)
.update(t + '.' + raw).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(v1);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b))
return res.status(401).end();
const event = JSON.parse(raw.toString('utf8'));
if (event.type === 'order.paid') {
const d = event.data;
// look up YOUR order by d.reference; verify d.amount + d.currency; mark paid (idempotent)
}
res.status(200).end();
});
data.amount and data.currency match what you expected for data.reference before fulfilling. Events are delivered at-least-once, so keep your "mark paid" step idempotent.
Payment-link button
A fixed-amount payment link stores its price server-side, so the URL carries no amount and needs no signature. Create one from the dashboard's Developers section (or POST /api/v1/payment-links), then drop the returned /p/<slug> URL onto any button, anywhere - it is tamper-proof by construction.
<a href="https://checkout.2.25.199.135.nip.io/p/cs_7hK2p…"
style="display:inline-block;padding:12px 20px;
background:#5D4BF6;color:#fff;border-radius:10px;
font:600 15px Inter,sans-serif;text-decoration:none">
Pay now
</a>
Create it with the API:
curl https://checkout.2.25.199.135.nip.io/api/v1/payment-links \
-H "Authorization: Bearer cdk_live_4f0b9c2e…" \
-H "Content-Type: application/json" \
-d '{"amount":2500,"currency":"gbp","description":"Tip jar"}'
# → { "id":"plink_…", "url":"https://…/p/cs_…", "dynamic":false, "amount":2500, "currency":"gbp" }
Dynamic signed links
A dynamic link lets Site A set the amount per click - for invoices, variable carts, pay-what-you-want. Because the amount travels in a buyer-facing URL, it must be signed. Create the link with allow_dynamic: true:
curl https://checkout.2.25.199.135.nip.io/api/v1/payment-links \
-H "Authorization: Bearer cdk_live_4f0b9c2e…" \
-H "Content-Type: application/json" \
-d '{"currency":"gbp","description":"Invoice","allow_dynamic":true}'
# → { "id":"plink_9f3c…", "url":"https://…/l/plink_9f3c…", "dynamic":true, … }
Signing recipe
Build a canonical string, pipe-joined in this exact order, and HMAC-SHA256 it with your cdk_live_ API secret as the key:
string = "<linkId>|<amount>|<currency>|<reference>"
key = your cdk_live_ API secret
sig = HMAC_SHA256(key, string) // lowercase hex
# amount = decimal integer minor units, currency lowercased,
# reference = the raw value or "" if omitted.
Append amount, currency, reference and sig to the link's /l/<id> base URL:
$apiSecret = 'cdk_live_4f0b9c2e…'; // your API key = the signing key
$linkId = 'plink_9f3c…';
$amount = 1999; // £19.99 in minor units
$currency = 'gbp';
$reference = 'INV-9';
$canonical = "$linkId|$amount|$currency|$reference";
$sig = hash_hmac('sha256', $canonical, $apiSecret);
$url = 'https://checkout.2.25.199.135.nip.io/l/' . $linkId
. '?' . http_build_query([
'amount' => $amount,
'currency' => $currency,
'reference' => $reference,
'sig' => $sig,
]);
// → https://…/l/plink_9f3c…?amount=1999¤cy=gbp&reference=INV-9&sig=4e0b…c7
amount=1999 to amount=199 without recomputing sig and the HMAC no longer matches - Chargedeck returns HTTP 400 and refuses the checkout. A dynamic link with a missing or invalid signature is never silently trusted. (Rotating your API key invalidates every outstanding signed link.)
WooCommerce plugin
The Chargedeck gateway plugin wires all of the above into WooCommerce automatically: it creates a checkout per order, redirects the buyer, and completes the WC order from the signed webhook - no code required.
Install
- Download
chargedeck-woocommerce.zip. - In WordPress admin go to Plugins → Add New → Upload Plugin, choose the zip, and Activate.
- Go to WooCommerce → Settings → Payments and enable the Chargedeck gateway.
- In the gateway settings, paste your API secret (
cdk_live_…) and your Webhook secret (whsec_…) - both from your Chargedeck dashboard's Developers section. Leave the API base URL ashttps://checkout.2.25.199.135.nip.io. - Back in the Chargedeck dashboard, set your webhook URL to the plugin's receiver route:
https://YOUR-STORE.com/wp-json/chargedeck/v1/webhook
POST /api/v1/checkouts with the WooCommerce order total (in minor units) and its order id as reference, then redirects the buyer to the hosted page. When payment completes, Chargedeck's signed order.paid webhook - verified and amount-checked by the plugin - marks the WooCommerce order paid. The browser return is never trusted for payment status.