Webhooks
Plonki can POST to your server when an order finishes, so you don't have to poll.
Set up
In the dashboard, open Settings, add an HTTPS URL and save. The signing secret (whsec_…) appears under it.
Events
order.completed | A registration or renewal finished. The domain is in the account. |
order.failed | The registry refused it. Any payment went back to the balance. |
Payload
json
{
"id": "evt_3k8m2q9w4x1n7b5v6c0z",
"type": "order.completed",
"created": 1794051896,
"data": {
"id": "ord_k2v9wq4x8n1m7b3c5d6f",
"type": "register",
"domain": "acmecoffee.com.au",
"years": 1,
"amount": 2399,
"currency": "AUD",
"status": "completed",
"payment": "balance",
"external_ref": "cust_8271",
"test_mode": false,
"next_step": "Done. Use point_domain or set_nameservers to connect it to a website.",
"created_at": "2026-11-08T01:23:45.000Z"
}
}Verify the signature
Each request has Plonki-Signature: t=<unix seconds>,v1=<hex>. v1 is HMAC-SHA256 of`${t}.${raw body}` with your signing secret. Compare in constant time and reject anything older than five minutes.
import crypto from "node:crypto";
export function verifyPlonki(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return ok && fresh;
}import hashlib, hmac, time
def verify_plonki(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
signed = f"{parts['t']}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return hmac.compare_digest(expected, parts.get("v1", "")) and freshfunction verify_plonki(string $rawBody, string $header, string $secret): bool {
parse_str(str_replace(',', '&', $header), $p);
$expected = hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret);
return hash_equals($expected, $p['v1'] ?? '') && abs(time() - (int) $p['t']) < 300;
}Delivery
- Answer with any 2xx within 10 seconds. Do slow work after responding.
- Failed deliveries retry with backoff (1, 2, 4, 8 minutes and so on) up to 8 attempts, roughly two hours in total.
- The same event can arrive more than once. Use
idto ignore repeats. - Events for test keys are sent too, with
test_mode: true.