Skip to main content

Concepts

What a webhook alert is

A webhook alert makes Zenskar send an HTTP POST to an endpoint you control whenever a chosen event occurs, such as a customer being created or an invoice being approved. Use it instead of polling the API for changes.

Events, categories, and subscriptions

Webhook lifecycle

A webhook is Active or Paused; pausing keeps its configuration and deleting removes it. See Webhook states.

How a delivery is produced and sent

When a subscribed event occurs, the originating action (for example, approving an invoice) hands it to Zenskar’s delivery service and then returns. The hand-off is best-effort: if it fails, the originating action still succeeds. The delivery service matches the event against every enabled webhook subscribed to it, builds a signed request for each, and POSTs it to the endpoint over HTTPS. Transient failures are retried a few times; see Delivery attempts. Webhooks push changes as they happen. Use the Zenskar API to read current state or to backfill events you missed.

Delivery sequence

Delivery guarantees

Your webhook handler must be idempotent. Delivery is at-least-once and unordered, so deduplicate on event_id and apply events by occurred_at, not arrival order.
For example, two customer.updated events applied in arrival order rather than by occurred_at leave you with the older values.

Endpoint safety

The endpoint URL must be HTTPS. The delivery service rejects a URL that is not HTTPS, that uses a raw private or reserved IP address, that is localhost, that ends in .local, .internal, or .localhost, or that is metadata.google.internal. URL structure is checked when you save the webhook. On every delivery the delivery service also resolves the hostname and rejects it if any resolved address is in a private or reserved range.

Signed deliveries

Every request carries an X-Signature header: sha256= followed by the lowercase hex HMAC-SHA256 of the exact request body, keyed with the webhook’s secret. The body is compact JSON: a space follows each : and ,, non-ASCII characters are escaped, and keys are in insertion order, not sorted. Recompute the signature over the bytes as received and compare in constant time before trusting the payload. A match proves the request came from Zenskar unaltered.

Events not shown in the form

invoice.cancelled is produced internally when a full credit note with a cancel intent cancels an invoice, but the delivery service has no mapping for it, so it is not delivered to webhook endpoints and has no checkbox. entitlement.expired is defined but not currently emitted.

Effective values in payloads

One payload field is normalized rather than copied raw. A contract’s status is delivered as its effective status: a contract past its end date reports expired even if its stored status is still active. Every other field is copied as stored. See App fields to payload keys for the field map.

How-to guides

Open webhook alerts

  1. Click the account menu at the bottom of the sidebar, and select Settings.
  2. Open the Webhook Alerts tab.
If the tab reports that you are not authorized, ask an administrator to grant your role the webhook permissions. See Permissions.

Create a webhook

  1. On Settings > Webhook Alerts, click Add webhook alert.
  2. Enter a Webhook Name.
  3. Optional: click + Add description and enter a description.
  4. Enter the Endpoint URL. It must be an HTTPS URL that is reachable from the public internet and accepts an HTTP POST. A URL that is not HTTPS, or that points at a private or internal address, is rejected.
  5. Enter a Secret Key. Use a long, random value, and store it where your endpoint can read it.
  6. Leave Enable Webhook Alerts checked to start delivering immediately, or uncheck it to create the webhook paused.
  7. Select at least one event (see Choose which events to subscribe to).
  8. Click Create.

Choose which events to subscribe to

Events are grouped by category. Expand a category and check events such as Customer Created or Invoice Approved; the count beside each category shows how many are selected. At least one event is required to save.

Edit a webhook

  1. On Settings > Webhook Alerts, open the actions menu on the webhook’s row, and select Edit.
  2. Change any field. The secret key is masked; enter a new value only to replace it.
  3. Click Update.

Pause or resume a webhook

Open the actions menu on the webhook’s row, and select Pause or Resume. A paused webhook delivers nothing until resumed.

Delete a webhook

  1. On Settings > Webhook Alerts, open the actions menu on the webhook’s row, and select Delete.
  2. Confirm.

Inspect delivery history

  1. On Settings > Webhook Alerts, click the webhook’s row.
  2. The webhook page shows its Endpoint, current Status, and the Tracking Events it subscribes to.
  3. Alerts History lists events delivered to this webhook, most recently attempted first, each marked Succeeded or Failed. Each entry is one event; retries and manual resends update the same entry in place rather than adding a new one.
  4. Select a delivery for Triggered Webhook Details (webhook ID, delivery status, last-updated time) and Event Information (the JSON body sent). A copy action copies the payload.

Resend a delivery

Open a delivery in Alerts History, then click Resend in Triggered Webhook Details. Zenskar re-sends the same event; only attempt_count changes, which also changes the signature.

Verify a signature

Compute the HMAC-SHA256 of the raw request body with the webhook’s secret, then compare it to the X-Signature header value after the sha256= prefix. Hash the bytes as received. Do not parse and re-serialize the JSON first: that changes the bytes and the hash.

Make your endpoint idempotent

A delivery can arrive more than once. event_id is unique per event: record the ones you have processed and skip repeats before doing work.

Apply events in timestamp order, not arrival order

Events can arrive out of order, and object payloads are full snapshots, so applying a late older event overwrites newer values. Track the newest occurred_at applied per object_id and skip anything older or equal:
Compare occurred_at only within the same object_id. Order by the top-level occurred_at. Some payloads also carry an updated_at inside event_info, but it is not present on every event and can differ slightly from occurred_at, so prefer the top-level field.

Respond correctly

Return HTTP 200, 201, 202, or 204 as soon as you have stored or queued the event, and process it asynchronously. Respond within 30 seconds. Any other status, a timeout, or a connection error marks the delivery Failed. Transient failures are retried; a 4xx response is treated as permanent and is not retried. See Delivery attempts.

Troubleshoot deliveries


Reference

Location

Settings > Webhook Alerts.

Permissions

Create and delete also require can_read_webhook. If the tab reports that you are not authorized, ask an administrator to grant these to your role.

Webhook list columns

The table on Settings > Webhook Alerts lists your webhooks with these columns: Click a row to open the webhook and its delivery history.

Webhook form fields

Webhook states

Deleting removes the webhook; it is not a state.

Form and payload names

Each checkbox is labelled <Category> <Action>, for example Payment Refunded for payment.refunded.

Event catalog

These are the event_triggered values delivered in the payload. In the webhook form the same events appear as checkboxes labelled by category and action, for example invoice.approved as Invoice Approved. invoice.cancelled and entitlement.expired have no checkbox, and invoice.cancelled is not delivered to endpoints.

Customer

Invoice

Contract

Payment

Entitlement

Usage Event Ingestion

Shown in the form as Usage Event Ingestion (S3).

Delivery request

Zenskar populates each delivery body with these fields, in this order: The body carries no organization identifier. Use a separate endpoint or secret per organization, or read the organization ID from event_info (organisation for customer, organisation_id for invoice and payment, organization_id for contract; entitlement and ingestion payloads carry none).

App fields to payload keys

event_info is a full snapshot of the object, minus internal and sensitive fields (raw billing data, internal identifiers). Each table maps the field as shown in the Zenskar app to its key inside event_info. A blank App field means the value has no single field in the app. For what each field means, see the module docs: Customers, Life cycle of an invoice, Contract, Payments, Entitlements.

Customer

Invoice

Contract

Payment

Entitlement

Entitlement payloads have no app form fields and carry no organization ID. For entitlement.granted, event_info is the full entitlement-customer record and object_id is that record’s ID. For entitlement.exhausted, event_info carries only entitlement_id and customer_id, every other key is null, and object_id is the entitlement definition ID.

Usage event ingestion

Sent when a usage-data file finishes ingesting. It has no form; the payload mirrors the ingestion result and carries no organization ID.

Delivery outcome

Delivery attempts

Delivery error codes

Shown on a failed delivery.

Signature

Delivery semantics