Overview

A webhook is push on top of the v1 API: the moment something happens in the organization, Smengo sends a POST to your URL. No need to poll /api/v1/* on a schedule.

Webhooks vs polling

Polling the API Webhooks
Who initiates you, on a schedule Smengo, when the event happens
Latency equals your polling interval a single change arrives within a minute
Idle cost requests keep going even when nothing changed no requests while there are no events
What you get the current state of a resource the fact of a change: type, ids, date

The event body is thin: identifiers and dates, no details. Details are read back with your API key — most events carry a ready-made related.url for exactly that. That way a webhook never exposes more than the API would to the same key, and the body cannot go stale between retries.

The usual integration shape: webhook as the trigger, API as the source of truth.

How to set it up

  1. Open Settings → Integrations → the Webhooks block. The section requires the Organization settings permission (manage_org).
  2. Click Add webhook, enter the receiver URL, tick the events and (optionally) a description.
  3. Copy the whsec_… secret — it is shown exactly once. Every request is signed with it, see Verifying the signature.
  4. Click Send test event — a service ping event goes out and your server's response code appears in the card right away.

You can only subscribe to events whose data you can see in Smengo yourself — see the event-to-permission table in Events. Unavailable checkboxes are disabled with an explanation.

A step-by-step guide for non-technical users is in the help center.

Delivery format

  • Method POST, body is compact JSON (no indentation, no line breaks).
  • Content-Type: application/json; charset=utf-8, User-Agent: Smengo-Webhooks/1.0.
  • https:// and port 443 only. The scheme and port are checked when the URL is saved and again before every delivery.
  • Redirects are not followed. A 3xx response is a failed delivery, not a hop to Location.
  • Timeout — 15 seconds for the whole delivery (connect, TLS, response).
  • Success is strictly 2xx. The response body has no effect on the outcome: we read at most 512 bytes of it — those go to the log for debugging, and the rest is never read at all (the connection is torn down at that point).
  • One event, one request. There is no batching.

Request headers

Header Value
Smengo-Event-Id Event UUID. One per event: if two endpoints subscribe to the same event, both receive the same Smengo-Event-Id. It never changes across retries — this is the deduplication key.
Smengo-Delivery-Id <delivery uuid>:<attempt number>, e.g. …:3. There is one delivery row per endpoint; a retry reuses it and only bumps the number.
Smengo-Event-Sequence Integer, monotonically increasing. Sort events by it, not by arrival time.
Smengo-Event-Type schedule.published, schedule_entry.changed, checkin.recorded, swap.decided or the service ping — routing without parsing the body.
Smengo-Timestamp Unix seconds of the signing moment. Same value as t= in the signature.
Smengo-Signature t=<unix>,v1=<hex>. During secret rotation there are two signatures separated by a space: t=…,v1=<hex> v1=<hex>. See Verifying the signature.

We also set Content-Length and Accept-Encoding: identity (we don't need a compressed response — only its first 512 bytes go to the log).

Example request

POST /hooks/smengo HTTP/1.1
Host: example.com
Content-Type: application/json; charset=utf-8
User-Agent: Smengo-Webhooks/1.0
Smengo-Event-Id: 3f1c8b52-9a4d-4f7e-b2c1-8e5d0a6b4c39
Smengo-Delivery-Id: 7b2e4d16-0c58-4a93-9f41-2d6b8c3e5a70:1
Smengo-Event-Sequence: 10427
Smengo-Event-Type: schedule_entry.changed
Smengo-Timestamp: 1785921323
Smengo-Signature: t=1785921323,v1=5a0f…c81b

{"id":"3f1c8b52-9a4d-4f7e-b2c1-8e5d0a6b4c39","type":"schedule_entry.changed","created":"2026-08-01T09:15:23.481Z","sequence":10427,"org_id":"9c1a2e34-5678-4abc-9def-0123456789ab","api_version":"v1","data":{"action":"updated","employee_id":"5e6d9f2a-3b7c-4e2f-9a1d-7c8e2f6b4a10","entry_date":"2026-08-14","entry_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"},"related":{"url":"https://smengo.com/api/v1/schedule-entries?from=2026-08-14&to=2026-08-14"}}

Event envelope

{
  "id": "3f1c8b52-9a4d-4f7e-b2c1-8e5d0a6b4c39",
  "type": "schedule_entry.changed",
  "created": "2026-08-01T09:15:23.481Z",
  "sequence": 10427,
  "org_id": "9c1a2e34-5678-4abc-9def-0123456789ab",
  "api_version": "v1",
  "data": { "action": "updated", "employee_id": "5e6d9f2a-3b7c-4e2f-9a1d-7c8e2f6b4a10", "entry_date": "2026-08-14", "entry_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" },
  "related": { "url": "https://smengo.com/api/v1/schedule-entries?from=2026-08-14&to=2026-08-14" }
}
Field Type Description
id uuid Same as Smengo-Event-Id.
type string Event type.
created string When the event was born, ISO 8601 UTC with a Z suffix and milliseconds.
sequence number Same as Smengo-Event-Sequence.
org_id uuid Source organization. Useful when one receiver serves several organizations.
api_version string The API version the identifiers and the link belong to — always v1 for now.
data object Contents depend on the event type — see Events. Keys inside data are sorted alphabetically.
related.url string A ready-made GET request to the v1 API for the details. Optional field: swap.decided, schedule.published and shift deletions don't have it — see Events.

The envelope key order is fixed (id, type, created, sequence, org_id, api_version, data, related) and identical across retries: the signature is computed over exactly the bytes that go into the body.

Instants inside data arrive the way the database emits them — ISO 8601 with an offset (2026-08-01T09:15:23.481+00:00) — whereas the envelope's created is always normalized to Z. Parse both with an ISO 8601 parser, not by comparing strings.

At-least-once and deduplication

Delivery is guaranteed at least once: an event may arrive again after a timeout, a dropped connection, or the recovery of a stuck delivery. That is normal operation, not a malfunction.

Deduplicate by Smengo-Event-Id. A practical shape: a unique index on that id on your side; a duplicate insert isn't an error but a signal of "already processed, reply 200".

Order is not guaranteed

Events may arrive in any order: one transaction produces a batch, deliveries run in parallel, and a retry can push a single event hours ahead.

To restore order use Smengo-Event-Sequence — an integer that increases monotonically within the organization. created won't do: all events of one transaction share it down to microseconds.

Receiver requirements

  • A public https URL on port 443. Addresses in private ranges (10/8, 127/8, 169.254/16, 192.168/16, IPv6 ULA and so on) are rejected — nothing is sent there even with a valid DNS name.
  • Don't filter by IP. We neither publish nor promise a fixed list of addresses deliveries come from: a request is proven genuine by its signature, not by its source address.
  • No user:pass@ in the URL and no #… fragment. The URL may be up to 2000 characters long.
  • Reply 2xx fast, process asynchronously. Verify the signature, enqueue the event, reply 200 immediately. If your business logic runs into the 15-second timeout, the delivery counts as failed and will come again.
  • Be idempotent — see deduplication above.
  • Don't rely on redirects. The URL must be served by the final server; we will not follow a 301/302 to a new path.
  • Reply 410 Gone only when the URL is dead for good: it is the one code that disables the webhook immediately.

The ping test event

The Send test event button sends a service event:

{
  "id": "…",
  "type": "ping",
  "created": "2026-08-01T09:15:23.481Z",
  "sequence": 10428,
  "org_id": "9c1a2e34-5678-4abc-9def-0123456789ab",
  "api_version": "v1",
  "data": { "test": true }
}

It is signed with the same secrets and carries the same headers as a real delivery — verify it with the same code. You cannot subscribe to ping: it is sent to one specific endpoint, including a paused one, and is never retried (exactly one attempt).

v1 limitations

  • Endpoints are managed in the UI only: there are no /api/v1/webhook-* REST endpoints, and the v1 API stays read-only.
  • A subscription covers the whole organization: there is no per-department filter.
  • There is no manual redelivery in the UI — read what you missed through the API.

Next

Was this article helpful?