Webhooks
Webhooks push notification events to your own systems as they happen: Hober sends an HTTPS POST to your endpoint the moment a subscriber is created, a send is dispatched, a campaign finishes, or a recipient interacts with a message. This is the same REST-hook surface that powers the Zapier integration — anything Zapier can react to, your own automation can too.
Webhooks are a plan feature. On plans without it, webhook endpoints cannot be created and the API responds 403 with "error": "feature_not_available". Each workspace can have up to 10 webhook endpoints.
1. Creating an endpoint
In the dashboard
Open Settings → Webhooks ("Receive HTTP callbacks when notification events occur."):
- Click Add webhook, enter your Endpoint URL (must be HTTPS), and tick the event types you want.
- On save, the endpoint's signing secret (
whsec_…) is shown once — store it now; it cannot be retrieved later. - Each endpoint row shows its subscribed events and offers Test (fires a test delivery to your URL) and Delete. A delivery log panel below shows recent webhook deliveries so you can debug your receiver.
Via the API
The REST-hook API authenticates with a server key (hober_srv_…, from Settings → API Keys → Server keys):
POST /v1/hooks
Authorization: Bearer hober_srv_...
Content-Type: application/json
{
"target_url": "https://example.com/webhooks",
"events": ["subscriber.created", "notification.terminal"]
}
A 201 response returns the endpoint — including the signing secret, this one time only:
{
"id": "hook-uuid",
"target_url": "https://example.com/webhooks",
"events": ["subscriber.created", "notification.terminal"],
"secret": "whsec_..."
}
Related endpoints:
| Method & path | Purpose |
|---|---|
GET /v1/hooks | List your endpoints (secrets omitted). |
DELETE /v1/hooks/:id | Remove an endpoint. |
GET /v1/hooks/samples/:event | Returns a one-element array of sample payload data for an event type — handy when building an integration before real traffic exists. |
The target URL must be a valid HTTPS URL, and every requested event type must be one of the known types below — anything else is rejected with a 422 validation error.
2. Event types
| Event type | Fires when |
|---|---|
subscriber.created | A net-new subscriber is created (updates to existing subscribers do not re-fire). |
notification.dispatched | A notification job is dispatched for sending. |
notification.terminal | A job reaches a terminal state (completed or failed) — final delivery counts included. |
notification.interaction | A recipient interacts with a message: tap, open, click, dismiss, and so on. |
notification.interaction can be high-volume — one event per recipient interaction. Subscribe an endpoint to it deliberately.
3. Payloads
Every delivery is a JSON POST with a common envelope; the data field carries the event-specific payload:
{
"id": "delivery-uuid",
"event_type": "notification.terminal",
"tenant_id": "tenant-uuid",
"timestamp": "2026-07-27T12:00:00Z",
"data": { }
}
subscriber.created
{
"subscriber_id": "0f8e7d6c-5b4a-4392-8171-6e5d4c3b2a19",
"external_id": "user-1042",
"email": "ana@example.com",
"attributes": { "plan": "growth", "first_name": "Ana" },
"created": true
}
notification.dispatched
{
"job_id": "job-uuid",
"target_size_estimate": 5000,
"scheduled_at": "2026-07-27T12:00:00Z"
}
notification.terminal
{
"job_id": "job-uuid",
"status": "completed",
"delivered": 1180,
"failed": 20,
"total": 1200
}
status is "completed" or "failed".
notification.interaction
{
"job_id": "job-uuid",
"interaction_type": "tapped",
"platform": "ios",
"variant_id": "variant-uuid"
}
interaction_typeis one of:foreground_received,tapped,action_tapped,dismissed,email_opened,email_clicked,inapp_impression,inapp_click,inapp_dismiss.platformis one of:ios,android,web,email,server.variant_idis present only for A/B variant sends.
4. Delivery, retries, and headers
Each delivery POSTs the JSON body with these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Hober-Event | The event type, e.g. notification.terminal. |
X-Hober-Delivery | A unique delivery ID (UUID) — use it for idempotency if you receive a retry. |
X-Hober-Signature | sha256= followed by a hex HMAC of the body (see below). |
Delivery semantics:
- A response with status 200–299 counts as delivered. Respond quickly (do heavy work asynchronously) — each attempt has a 10-second timeout.
- On a 5xx response or a network error, delivery is retried up to 3 more times with backoff of 1s, 2s, then 4s (4 attempts total).
- A 4xx response is treated as a permanent failure for that delivery and is not retried.
- Every attempt is recorded; the dashboard's webhook delivery log shows the payload, attempt count, last status code, and outcome per delivery, and each endpoint tracks its consecutive-failure count.
5. Verifying signatures
Always verify X-Hober-Signature before trusting a payload. The signature is an HMAC-SHA256 over the raw request body. The HMAC key is not the secret itself but the hex-encoded SHA-256 digest of your whsec_… secret string:
key = hex( SHA-256( "whsec_..." ) ) // your stored secret, hashed once
expected = "sha256=" + hex( HMAC-SHA256(key, raw_body) )
For example, in Go:
sum := sha256.Sum256([]byte(secret)) // secret = "whsec_..."
key := hex.EncodeToString(sum[:])
mac := hmac.New(sha256.New, []byte(key))
mac.Write(body) // raw request body bytes
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
valid := hmac.Equal([]byte(expected), []byte(r.Header.Get("X-Hober-Signature")))
Compare with a constant-time comparison, and reject the request if the signature does not match.
6. Notes
- Zapier: the Zapier app's instant triggers (New Subscriber, Campaign Finished) are built on exactly these hooks.
- Slack summaries are a separate feature: the Slack integration posts formatted campaign summaries to a Slack channel and is configured independently — it is not part of the webhook surface described here.
- Sending data in: webhooks are outbound. To push events into Hober (for segments, journey triggers, and personalization), use the Events API with the same server key — see Server-Side Event Tracking.