חשבוניות SLK
All API pages

API reference

Webhooks

נסו ב-Playground
On this page

SLK Invoices POSTs a signed JSON event to your server when something happens in the business. Use it to sync documents to your own system, trigger fulfilment, or drive Zapier/Make ("Catch Hook").

Manage endpoints in the dashboard at /billing/settings/webhooks: add an https URL, choose the events, and copy the signing secret (shown once when created or regenerated).

Events#

EventFires when
document.issuedAny document is issued — from the dashboard, the API, a connector, or the mirror credit note of a cancellation.
document.cancelledA fiscal document is cancelled (its mirror credit is issued), or a non-fiscal document is cancelled directly.
document.paidA referenced document's open balance reaches 0 as a result of an issue: a receipt settling an invoice, or a credit note crediting it in full. The event carries the settled document.
customer.createdA customer card is created in the dashboard. (POST /customers does not fire it.)
customer.updatedA customer card is saved in the dashboard.
proposal.approvedA customer digitally approves a quote.

Webhooks need a plan that includes them; on other plans endpoints stay stored but nothing is dispatched.

The envelope#

Every delivery body is:

json
{
  "id": "b6e2c1d4-8f3a-4e7b-9c21-5d0a7f6e3b18",
  "event": "document.issued",
  "created_at": "2026-09-29T10:00:00.000Z",
  "tenant_id": "3c8f…",
  "data": { }
}

id is a UUID per event; retries and manual re-sends of the same event carry the same id, so de-duplicate on it. sandbox: true is added at the top level for events minted by sandbox mode (never a real document).

data for document.*#

json
{
  "id": "3f2b7c1e-9d4a-4c55-8a51-6d1e0f6b7a10",
  "docType": 1,
  "docNumber": 1042,
  "status": 1,
  "customerId": null,
  "customerName": "אורן כהן — סטודיו לעיצוב",
  "totalAgorot": 450000,
  "balanceAgorot": 450000,
  "currency": "ILS",
  "issueDate": "2026-09-29"
}

Sandbox document.issued events are slimmer: id is sbx_<uuid>, docNumber is "SBX-000003", status is 0, and balanceAgorot / issueDate are absent.

data for customer.*#

json
{ "id": "9c1d…", "name": "נועה לוי", "email": "[email protected]", "uniqueId": "301234567", "active": true }

data for proposal.approved#

json
{
  "proposalId": "…", "documentId": "…", "docNumber": 17, "docType": 7,
  "customerName": "…", "totalAgorot": 118000,
  "approverName": "…", "approverIp": "…", "approvedAt": "2026-09-29T10:00:00.000Z",
  "publicUrl": "https://…/q/…"
}

Headers#

HeaderValue
Content-Typeapplication/json
X-SLK-Eventthe event name
X-SLK-Delivery-Idid of this delivery
X-SLK-Signaturet=<unix seconds>,v1=<hex>

Verifying the signature#

v1 is HMAC-SHA256(secret, "<t>.<rawBody>") as lowercase hex, where rawBody is the exact bytes of the request body and secret is the endpoint's signing secret. Always:

  1. Read the raw body before parsing JSON (re-serialising changes the bytes).
  2. Recompute and compare in constant time.
  3. Reject when t is too old (5 minutes is sensible) to stop replays.
  4. Respond 2xx quickly and process asynchronously.

Node#

js
import crypto from "node:crypto";

export function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  if (!parts.t || !parts.v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > toleranceSec) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(parts.v1);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: app.post("/hook", express.raw({ type: "application/json" }), (req, res) =>
//   verify(req.body.toString("utf8"), req.get("X-SLK-Signature"), SECRET) ? res.sendStatus(200) : res.sendStatus(401));

PHP#

php
function slk_verify(string $raw, string $header, string $secret, int $tolerance = 300): bool {
    parse_str(str_replace(',', '&', $header), $p);              // t=…&v1=…
    if (empty($p['t']) || empty($p['v1']) || abs(time() - (int)$p['t']) > $tolerance) return false;
    return hash_equals(hash_hmac('sha256', $p['t'] . '.' . $raw, $secret), $p['v1']);
}
$raw = file_get_contents('php://input');
if (!slk_verify($raw, $_SERVER['HTTP_X_SLK_SIGNATURE'] ?? '', getenv('SLK_WEBHOOK_SECRET'))) { http_response_code(401); exit; }

Python#

python
import hashlib, hmac, time

def verify(raw: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    p = dict(kv.split("=", 1) for kv in header.split(",") if "=" in kv)
    if "t" not in p or "v1" not in p or abs(time.time() - int(p["t"])) > tolerance:
        return False
    expected = hmac.new(secret.encode(), p["t"].encode() + b"." + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, p["v1"])

The SDKs ship this as verifyWebhook / verify_webhook, and the Postman collection contains a pre-request script that signs a test delivery the same way.

Delivery and retries#

  • Timeout: 10 seconds per attempt. Redirects are not followed (a 3xx counts as a failure).
  • Any non-2xx response, a timeout or a network error schedules a retry at 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours after the previous failure. After the last retry the delivery is marked failed; it can be re-sent by hand from the delivery log.
  • An endpoint that fails 20 deliveries in a row is disabled automatically; one success resets the streak. Re-enable it in the dashboard.
  • Destinations must be public https URLs: no localhost, internal hostnames or private IP addresses, and the address is re-checked at every send.
  • Deliveries are sent right after the event and swept by a scheduled job; order is not guaranteed across events, so use created_at and the document status to reconcile.

Testing#

  • "שליחת אירוע בדיקה" on the webhooks screen sends a synthetic document.issued (sandbox: true, docNumber: 0) to your endpoints immediately.
  • In a sandbox account, every API-issued SBX- document fires document.issued with sandbox: true, so you can test the full path without issuing anything real.
  • Local development: expose your machine with a tunnel that gives a public https URL.
Webhooks — SLK Invoices API