Skip to content

Webhooks

Webhooks send an HTTPS POST to your endpoint when something happens in Termpact. Add endpoints in Settings → Webhooks (Owner or Admin). Webhooks are part of the Pro plan.

Event Sent when
invoice.created An invoice is issued, or an edited order replaces one (supersedes_id is set)
payment.applied A payment is recorded and applied to invoices
hold.changed An order or location hold is placed or released
application.approved A trade account application is approved
POST /your/path HTTP/1.1
Content-Type: application/json
User-Agent: Termpact-Webhooks/1
Termpact-Event: invoice.created
Termpact-Delivery: 5c1e…
Termpact-Signature: t=1767225600,v1=4f7c…
{
"id": "0b6f…",
"type": "invoice.created",
"created_at": "2026-10-27T14:03:11.000Z",
"shop_domain": "acme.myshopify.com",
"api_version": "v1",
"sequence": 1042,
"data": { "invoice": { "id": "…", "number": "INV-1042", "total": { "amount": "1250.00", "currency": "USD" } } }
}
  • id is the event id. A replay or a retry sends the same id, so use it to ignore duplicates.
  • sequence goes up by one for each event your store sends. Events can arrive out of order; sort by sequence if order matters.
  • data uses the same shapes as the API.

Reply with any 2xx within 10 seconds. Do the work after replying if it takes longer.

The Termpact-Signature header holds a timestamp t and one or more v1 signatures. Each v1 is the hex HMAC-SHA256 of "<t>.<raw body>" with your endpoint’s signing secret. Accept the request when any v1 matches and t is within 5 minutes of your clock.

import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(header, rawBody, secret, nowSeconds = Math.floor(Date.now() / 1000)) {
const parts = header.split(",").map((p) => p.trim().split("="));
const t = Number(parts.find(([k]) => k === "t")?.[1]);
if (!Number.isInteger(t) || Math.abs(nowSeconds - t) > 300) return false;
const want = Buffer.from(createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex"), "hex");
return parts.some(([k, v]) => k === "v1" && /^[0-9a-f]{64}$/.test(v) && timingSafeEqual(Buffer.from(v, "hex"), want));
}

Use the raw request body exactly as received. Parsing and re-serializing the JSON changes the bytes and the check fails.

Rotate secret creates a new secret and shows it once. For 24 hours each request carries two v1 signatures, one per secret, so you can update the receiver without missing anything.

A delivery that doesn’t get a 2xx is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours. After that it shows as Gave up in the delivery log.

If an endpoint keeps failing for 3 days, Termpact switches it off and tells you in the app and by email. Fix the receiver, Turn on the endpoint, then use Replay in the delivery log for anything it missed.

Endpoints must use HTTPS and a public address. Private, loopback, link-local and other reserved addresses are refused, and the host name is checked again on every send. Redirects are not followed.