Introduction
Receive signed workspace events securely in your backend, with a complete event catalog and delivery contract.
What this page covers
- A log is Hoko's small, immutable activity fact: what happened, which record it affected, who performed it, and when.
- A webhook delivery is one outbound attempt to one receiver. It may be absent, pending, Unconfirmed, succeeded, or failed without changing whether the original activity was committed.
Unconfirmedis the dashboard label for the persistedunknownstate. - A payload is the same thin message contract for every subscribed event, including
lead.createdandsale.created.
Webhooks are signed server-to-server notifications from Hoko. When a subscribed workspace action is committed, Hoko sends a best-effort HTTPS POST to each eligible webhook. Use webhooks to synchronize your system or start work in your backend; use Logs to inspect the workspace activity fact that caused the message.
The two concepts are related but not interchangeable:
The Webhooks help guide covers the dashboard flow. This page is the technical reference for access, every selectable event, signatures, payloads, receiver behavior, and limits.
Identifiers
Public webhook payloads use bare 26-character ULIDs. Examples use typed placeholders such as <event_id> and <link_id>; actual payloads contain the corresponding public IDs. Storage identifiers are not part of the public contract.
Access and plan limits
Manage webhooks in Dashboard → Integrations → Webhooks when webhooks are enabled in the deployment. Access requires an active account with owner membership in every active collection of the workspace, with at least one active collection. Blocked or deleted memberships do not qualify. Webhooks are workspace-wide; choosing a collection does not scope a webhook to that collection. There is no public webhook-management API, and an API key cannot authorize dashboard management.
Business, Enterprise, and Elite allow five additional storage slots for pending and disabled replacement webhooks, so the total stored configurations are 15, 25, and 45. A downgrade preserves excess configurations but suspends delivery to webhooks above the new active allowance. An upgrade makes eligible active webhooks available again; missed activity is not replayed.
The active webhook ceilings are Business 10, Enterprise 20, and Elite 40.
Create and verify a webhook
- Prepare a public HTTPS receiver. Localhost, private, link-local, loopback, ambiguous IP, and redirect destinations are rejected by SSRF controls. 2. In the dashboard, enter a name and receiver URL, choose one or more events from the catalog below, and decide how to set the signing secret. Paste an existing canonical
whsec_secret, use the shuffle control to generate one, or leave the field blank for Hoko to generate one. 3. Create the webhook. It starts aspending_verification. Copy the signing secret immediately and store it in server-only secret storage; it cannot be reopened from the dashboard. 4. Implement signature verification and the exactendpoint.verificationchallenge response before selecting Verify webhook. 5. A successful verification changes the webhook toactive. Only active, plan-eligible webhooks receive subscribed workspace events. 6. Send a test and inspect its delivery. A test is a real outbound request; handleendpoint.testwithout triggering customer-facing business effects.
Owner actions are limited to 10 verification attempts per hour and 30 combined test-send and resend actions per hour. Rate-limited actions return 429 with Retry-After.
Event catalog
The shared event catalog lists every workspace event available to Webhooks and Logs. Use it to understand each scope and action, then copy the exact event name from the table.
Payloads
Every selectable workspace event follows one payload contract: the same four-field JSON shape and JSON Schema version, 1.0. The version is in the published schema URL, not in the message body. lead.created and sale.created use this same contract; they do not include conversion details or record snapshots.
{
"id": "<event_id>",
"type": "utmTemplate.created",
"created": "2026-09-23T10:30:00.000Z",
"data": {
"id": "<utm_template_id>",
"workspaceId": "<workspace_id>"
}
}Every selectable event schema has the same required fields and no additional payload fields. Use the scope page for an event’s affected record and example. Fetch more information from the relevant API when available. Schema documents are published at /schemas/webhooks/<event-name>/1.0.json.
Webhook test and verification messages
endpoint.test uses the same top-level fields and data contains only the webhook id and workspaceId. endpoint.verification also uses the same top-level fields, with challenge and expiresAt added to data because the receiver must echo the challenge before the webhook can be activated. These are operational messages, not selectable workspace events.
{
"id": "<event_id>",
"type": "endpoint.verification",
"created": "2026-09-23T10:30:00.000Z",
"data": {
"id": "<webhook_id>",
"workspaceId": "<workspace_id>",
"challenge": "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8",
"expiresAt": "2026-09-23T10:35:00.000Z"
}
}Verify requests
Download the tested Node standard-library receiver example. It reads HOKO_WEBHOOK_SECRET server-side and does not depend on a third-party package.
Each request includes content-type: application/json and:
Your receiver must:
- Read the exact raw request bytes before parsing JSON. 2. Reject bodies over 64 KiB and timestamps outside ±300 seconds. 3. Compute HMAC-SHA256 over
webhook-id + "." + webhook-timestamp + "." + exactRawBodyusing the canonical 32-bytewhsec_key. 4. Compare equal-length signatures withtimingSafeEqual. 5. Parse JSON only after verification and require itsidto equalwebhook-id. 6. Forendpoint.verification, return exactly{"challenge":"<received challenge>"}as JSON. 7. Atomically record a durable unique receipt keyed bywebhook-idwhile persisting or queueing your work. 8. Return2xxquickly. A duplicate ID must also return2xxwithout repeating its side effect.
An in-memory Set is not production deduplication. Use a database unique constraint or another durable atomic store. Verification must happen before any receipt, persistence, queue, or business side effect. Never parse and reserialize a body before verifying it; whitespace changes invalidate the signature.
Delivery contract and limits
pending: the delivery row exists, but no attempt has started.- Unconfirmed (
unknown): Hoko claimed the attempt but saved no terminal result. Hoko cannot prove whether the receiver received it, so this state is never reported as success or failure. succeeded: Hoko recorded a successful sender result, not proof of downstream business completion.failed: Hoko recorded a failure category such as timeout, DNS, TLS, network, unsafe destination, invalid challenge, or non-2xx response.
Hoko writes the log and delivery row in the same transaction as the originating mutation, then awaits one best-effort outbound attempt from the committed request. Receiver failure does not roll back an accepted Hoko mutation. A rolled-back mutation sends no webhook.
Delivery is immediate and request-bound, not a durable background queue. Business-plus workspaces can use webhooks, subject to their webhook allowance. Hoko supports fan-out to up to 40 active webhooks and keeps at most 1,000 delivery candidates in request memory. Large bulk operations can leave additional rows pending when the three-second receiver budget expires. Hoko makes one automatic attempt for each delivery, with no automatic retries or ordering guarantee. Owners can send an eligible delivery again from the log row's More options menu.
The Webhooks dashboard side sheet displays the receiver URL and the 10 most recent attempts for the selected webhook. See all deliveries opens the dedicated per-webhook delivery history page from either the row's More options menu or the side sheet. That page uses paginated loading and supports sorting and filtering, so older attempts are loaded only when requested.
Delivery states have these fixed meanings:
The recorded categories are success, unsafe_destination, dns_failure, invalid_message, oversized_request, timeout, tls_failure, network_failure, non_2xx, invalid_challenge, response_too_large, and pinning_unavailable. Only success maps to succeeded; every other recorded category maps to failed. An Unconfirmed attempt has no category because Hoko saved no final result.
Requests are limited to 64 KiB and three seconds of receiver time. Database work adds time to the originating request. There is no monthly webhook quota or overage billing; attempts are separate from tracked clicks and conversion usage. Hoko keeps delivery history for the workspace lifetime, including after a webhook is deleted.
Resend a delivery
Open Logs, find the event, and choose More options → Resend on a pending, Unconfirmed, or failed delivery. A pending delivery reuses attempt 1. An Unconfirmed delivery can be resent after a five-minute cooldown. A failed delivery creates the next attempt number. Hoko records every result alongside the original result. A successful delivery cannot be resent from the dashboard.
The resend uses the original event ID and a new signature timestamp. A targeted webhook test resends its original endpoint.test body. A log-backed broadcast keeps its original event type and body. Receivers must treat webhook-id as a durable idempotency key because an earlier attempt may have reached the receiver before Hoko recorded its result. Resending requires an active, plan-eligible webhook.
Lifecycle, replacement, and troubleshooting
The webhook moves through pending_verification, active, disabled, or deleted. Disabling stops new fan-out and cannot recall an already-sent request. To change the receiver URL, first disable the webhook and save the new URL. It returns to pending_verification while Hoko keeps the webhook ID, delivery history, and signing secret. Verify the new destination to activate it. To change the webhook name, subscribed events, or signing secret, create and verify a replacement, switch traffic, then disable and delete the old webhook. If both are active, the receiver may see the same event through both.
Select Delete webhook when you no longer need the configuration. Deletion hides the webhook from management, preserves its name and delivery history, and erases signing material. It does not remove ordinary workspace log facts. Deleted webhooks cannot deliver or be resent.
For a missing event, check the webhook is active, the event is selected, the action happened in this workspace, the plan still allows the webhook, and the delivery result. Old activity is not sent retroactively. For a failed delivery, fix the receiver and use More options → Resend. For an Unconfirmed delivery, assume the receiver may have applied the event, use the durable event-ID receipt to avoid duplicate processing, and resend only if needed.
Security and data boundaries
During local development, expose the receiver through a public HTTPS tunnel. Do not disable signature verification to make a tunnel work.
Keep HOKO_WEBHOOK_SECRET and every whsec_ signing secret in server-only secret storage. Never put a signing secret in browser code, a client bundle, localStorage, analytics, client logs, or a public repository.
Payloads intentionally exclude record snapshots, customer name, email, phone, avatar, conversion metadata, API keys, signing secrets, destination credentials, full response bodies, raw IP addresses, exact location, visitor or session identifiers, the full referrer, destination query strings, and other database-row fields. Treat the included identifiers as sensitive business data. Do not log raw bodies, signature headers, secrets, URL query values, or challenge values.
The canonical JSON and JavaScript examples are locale-neutral. Additional-language examples are not currently published.