API reference

Webhooks

Get notified when things happen. Add HTTPS endpoints below and subscribe each to the events you care about (or '*' for all). Tax Star POSTs a signed JSON payload to your URL on every invoice status change — sending and receiving. Each delivery carries an X-Taxstar-Signature header: 't=<unix_ts>,v1=<hex>' where <hex> is HMAC-SHA256 of '{t}.{raw_body}' keyed with your signing_secret; respond with a 2xx to acknowledge. Webhooks are managed right here — sign in, add your URL, send a test delivery to verify the connection, and track deliveries in the charts.

Manage webhooks

Add webhook endpoints to your application, then send a test event to verify the connection before going live. Tax Star will POST a signed payload to your URL on every invoice status change — sending and receiving.

Sign in with your Tax Star developer account to manage your webhooks.

How delivery works

For each event matching one of your subscriptions, Tax Star sends an HTTP POST to your url with a JSON body and an X-Taxstar-Signature header. Return a 2xx status to acknowledge. Always verify the signature before trusting a payload.

Example event payload
{
  "event": "invoice.sent",
  "created_at": "2026-06-21T10:00:00Z",
  "data": {
    "direction": "sent",
    "invoice_id": "6a3e3e160c9fbfd76f261711",
    "invoice_number": "INV-1001",
    "entity_id": "6a3e388e7a5b528a20bc5149",
    "status": {
      "code": "SENT_TO_PEPPOL",
      "message": "Invoice sent to Billberry successfully",
      "created_at": "2026-06-21T10:00:00Z"
    }
  }
}
Verifying the signature
// Node.js (Express) — verify an incoming Taxstar webhook
import crypto from "crypto";

function verify(req, signingSecret) {
  // header: "X-Taxstar-Signature: t=<unix_ts>,v1=<hex hmac-sha256>"
  const header = req.headers["x-taxstar-signature"] || "";
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("="))
  );
  const t = parts.t;
  const signed = `${t}.${req.rawBody}`; // raw, unparsed JSON body
  const expected = crypto
    .createHmac("sha256", signingSecret)
    .update(signed)
    .digest("hex");
  // constant-time compare; optionally reject if |now - t| is too large
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1 || "")
  );
}

Received-invoice events carry invoice_number and receiver_registry_code in data instead of invoice_id, with direction: "received".