Skip to main content

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.

Plan availability

Webhooks are a plan feature. On plans without it, webhook endpoints cannot be created and the API responds 403 with "code": "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."):

  1. Click Add webhook, enter your Endpoint URL (must be HTTPS), and tick the event types you want.
  2. On save, the endpoint's signing secret (whsec_…) is shown once — store it now; it cannot be retrieved later.
  3. 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 & pathPurpose
GET /v1/hooksList your endpoints (secrets omitted).
DELETE /v1/hooks/:idRemove an endpoint.
GET /v1/hooks/samples/:eventReturns 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 typeFires when
subscriber.createdA net-new subscriber is created (updates to existing subscribers do not re-fire).
subscriber.confirmedSomeone confirms a subscription form's double opt-in. Fires once for each subscriber the confirmation applied to: every subscriber holding the address, or the new one the form created.
notification.dispatchedA notification job is dispatched for sending.
notification.terminalA job reaches a terminal state (completed or failed) — final delivery counts included.
notification.interactionA 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
}

subscriber.confirmed​

{
"subscriber_id": "0f8e7d6c-5b4a-4392-8171-6e5d4c3b2a19",
"external_id": "hf_01JA2Q8Z6W3N5V7K9M1P4R6T8X",
"email": "ana@example.com",
"form_id": "5c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"provisional": true,
"attributes": { "interests": ["running", "yoga"], "first_name": "Ana" },
"created": true
}

created is true when the form created the subscriber; a new subscriber also fires subscriber.created. provisional is true while the subscriber carries the hf_… id the form assigned, before your own system identifies them. The marketing opt-in is recorded on the email channel before the event fires.

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_type is one of: foreground_received, tapped, action_tapped, dismissed, email_opened, email_clicked, inapp_impression, inapp_click, inapp_dismiss.
  • platform is one of: ios, android, web, email, server.
  • variant_id is present only for A/B variant sends.

4. Delivery, retries, and headers​

Each delivery POSTs the JSON body with these headers:

HeaderValue
Content-Typeapplication/json
X-Hober-EventThe event type, e.g. notification.terminal.
X-Hober-DeliveryA unique delivery ID (UUID) — use it for idempotency if you receive a retry.
X-Hober-Signaturesha256= followed by a hex HMAC of the body (see below).
X-Signature-256Legacy alias carrying the same value as X-Hober-Signature.

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.