Skip to content
Start free in Cloud

Signed Outbound Webhooks in HitKeep

External systems often need to react when a HitKeep site, goal, import, team, or user changes. Polling the REST API adds delay and repeated work. The HitKeep 2.11 webhook contract lets an owner or administrator subscribe an HTTPS endpoint to the operational events that matter.

Each delivery contains a versioned JSON body and four verification headers. HitKeep signs the exact request body with a one-time secret, retries unsuccessful deliveries, and keeps delivery outcomes for inspection. Delivery is at least once and ordering is not guaranteed, so receivers must verify, deduplicate, and process events idempotently.

Read and verify the exact body bytes before decoding JSON. This example uses a five-minute timestamp tolerance as a receiver policy. Store processed event or delivery IDs in durable storage before returning a successful response.

package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"log"
"net/http"
"strconv"
"strings"
"time"
)
const signingSecret = "whsec_replace_with_your_one_time_secret"
func main() {
http.HandleFunc("POST /hitkeep/webhook", receiveHitKeepWebhook)
log.Fatal(http.ListenAndServe(":8081", nil))
}
func receiveHitKeepWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
http.Error(w, "invalid body", http.StatusBadRequest)
return
}
timestamp := r.Header.Get("X-HitKeep-Timestamp")
seconds, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
http.Error(w, "invalid timestamp", http.StatusUnauthorized)
return
}
delta := time.Since(time.Unix(seconds, 0))
if delta < -5*time.Minute || delta > 5*time.Minute {
http.Error(w, "stale timestamp", http.StatusUnauthorized)
return
}
signature := r.Header.Get("X-HitKeep-Signature")
if !strings.HasPrefix(signature, "v1=") {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
provided, err := hex.DecodeString(strings.TrimPrefix(signature, "v1="))
if err != nil {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
mac := hmac.New(sha256.New, []byte(signingSecret))
mac.Write([]byte(timestamp + "."))
mac.Write(body)
if !hmac.Equal(mac.Sum(nil), provided) {
http.Error(w, "invalid signature", http.StatusUnauthorized)
return
}
eventID := r.Header.Get("X-HitKeep-Event-ID")
deliveryID := r.Header.Get("X-HitKeep-Delivery-ID")
_ = eventID
_ = deliveryID
// Decode the JSON body, apply the event idempotently, and persist the ID.
w.WriteHeader(http.StatusNoContent)
}

Do not parse and re-serialize the JSON before signature verification. Whitespace and object-key ordering are part of the signed bytes. This example listens on plain HTTP for local development; publish the production receiver through an HTTPS endpoint that meets HitKeep’s destination rules.

Webhook administration is a human dashboard workflow. API clients cannot create or rotate webhooks.

  1. Open Integration -> Webhooks.
  2. Select Site for one site’s events or Instance for cross-site and administrative events.
  3. Choose Create webhook, enter the HTTPS destination, and select at least one event.
  4. Save the signing secret immediately. HitKeep shows it only after creation or rotation.
  5. Use Send test and confirm that the receiver returns a 2xx response.
  6. Open Deliveries to inspect status, attempt count, response code, and retry outcomes.
HitKeep Create webhook dialog with name, destination URL, description, enabled state, and site event checkboxes
Create and edit webhooks in the standard HitKeep dialog, with the valid event catalog loaded for the selected scope.
HitKeep configured webhooks table showing a searchable endpoint, separated URL origin and path, enabled status, pagination, and a subscribed-events popover
The webhook list follows the shared searchable and sortable table pattern. Open the event count to inspect the exact subscription without expanding the row.

Instance owners and instance administrators can manage instance webhooks. Site owners and site administrators can manage webhooks for their sites; instance owners and administrators can also manage site webhooks. Editors, viewers, instance users, and delegated API clients do not receive webhook-management permission. See HitKeep roles and webhook permissions for the broader authorization model.

Header Meaning
Content-Type Always application/json
User-Agent HitKeep-Webhook/<major.minor> for the running release line
X-HitKeep-Timestamp Unix timestamp in seconds, included in the signed message
X-HitKeep-Signature v1=<hex HMAC-SHA256> over timestamp + "." + exact_body
X-HitKeep-Event-ID Stable event ID. Use this to deduplicate the same event across retries or endpoints when appropriate.
X-HitKeep-Delivery-ID Stable delivery ID for one endpoint subscription. It is also present as delivery_id in the body.

Use a constant-time comparison such as Go’s hmac.Equal. Reject missing or stale timestamps before doing application work. Keep the timestamp tolerance tight enough for your network path and clock synchronization.

Every delivery uses this envelope:

{
"api_version": "2.11",
"id": "5ec7ee31-7ddd-4bd9-a18d-0fb2d5730cc4",
"delivery_id": "1d1b4f1d-693e-4534-8c2f-03d40ac11617",
"type": "goal.converted",
"created_at": "2026-07-10T14:30:00Z",
"data": {
"site_id": "cf4b9cab-6fa7-4288-b1ab-eac93bd46553",
"goal_id": "6407f555-251a-44f9-a0aa-71c70b354859",
"goal_name": "Trial signup",
"goal_type": "event",
"converted_at": "2026-07-10T14:30:00Z"
}
}

api_version is the HitKeep major.minor release line that defines the webhook contract. id identifies the operational event. delivery_id identifies its delivery to one webhook. The fields inside data depend on the event type.

Use the HitKeep REST API reference for the current WebhookEventPayload, webhook-management, and delivery-history schemas. A running instance exposes the same runtime contract at /api/docs/v1/openapi.json.

Site webhooks receive events for one site:

Event When it is emitted
site.updated Site configuration changes
site.deleted A site deletion completes
goal.created A goal is created
goal.updated A goal is updated
goal.deleted A goal is deleted
goal.converted A tracked pageview or custom event matches a goal
import.completed An analytics import completes
import.failed An analytics import fails
webhook.test An administrator queues a test delivery

Instance webhooks can subscribe to every site event above, plus these instance events:

Event When it is emitted
site.created A site is created
system.user.updated An instance user changes
system.user.deleted An instance user is deleted
team.created A team is created
team.updated Team configuration changes
team.archived A team is archived
team.member.added A member joins a team
team.member.removed A member leaves or is removed from a team

The dashboard loads the event catalog for the selected scope, so it does not offer invalid combinations. The server also validates subscriptions on create and update.

Behavior Default
Success response Any HTTP 2xx status
Request timeout 10 seconds
Maximum attempts 6 total attempts
Initial retry delay 30 seconds
Maximum retry delay 6 hours
Global delivery concurrency 8
Per-endpoint concurrency 1
Delivery-history retention 30 days
Recovery sweep Every 30 seconds
Redirects Not followed

Retries use exponential backoff capped by the configured maximum. Delivery is at least once, and different events may arrive out of order. A receiver should therefore:

  1. Verify the timestamp and signature against the exact body.
  2. Check whether the event or delivery ID was already processed.
  3. Apply the change idempotently in a transaction or durable queue.
  4. Persist the processed ID.
  5. Return 2xx only after durable acceptance.

HitKeep stores safe delivery outcomes and attempt metadata. The dashboard does not return saved request bodies or signing secrets in delivery history.

HitKeep webhook delivery history dialog showing a successful webhook.test delivery, one attempt, HTTP 204 response, sortable columns, and pagination
Delivery history is paginated and sortable, with the event, terminal status, attempt count, response status, and creation time visible at a glance.

Production destinations must use HTTPS. HitKeep rejects URLs with credentials or fragments and blocks destinations that resolve to loopback, private, link-local, multicast, documentation, or other reserved addresses. It validates the destination when configured and again before delivery, uses direct DNS resolution, disables proxy inheritance for the webhook transport, and does not follow redirects.

For an explicit local development test, HITKEEP_WEBHOOK_ALLOW_DEVELOPMENT_TARGETS=true permits HTTP and private destinations. Do not enable it on a production instance. It removes the outbound destination protections that prevent server-side request forgery.

See the HitKeep security model for the complete list of optional outbound connections and restricted-network considerations.

Choose Rotate secret when a receiver secret may be exposed or as part of a regular credential policy. HitKeep replaces the configured secret, re-signs pending, retrying, and processing delivery records with the new value, and shows the replacement once. A request already in flight may still carry the previous signature, so coordinate a short overlap window at the receiver when required.

Deleting a webhook stops new subscriptions. Existing delivery outcome records remain until retention cleanup. Because HitKeep must sign deliveries, configured and snapshotted delivery secrets are stored in the HitKeep database. Protect the full data directory and every backup as credential-bearing data.

Most installations can keep the defaults. Operators with slow receivers or strict concurrency limits can tune the outbound worker through the configuration reference for webhook delivery.

Environment variable Default Purpose
HITKEEP_WEBHOOK_ALLOW_DEVELOPMENT_TARGETS false Allow HTTP and private destinations for explicit development testing
HITKEEP_WEBHOOK_DELIVERY_TIMEOUT 10 Request timeout in seconds
HITKEEP_WEBHOOK_DELIVERY_CONCURRENCY 8 Maximum concurrent outbound deliveries
HITKEEP_WEBHOOK_PER_ENDPOINT_CONCURRENCY 1 Maximum concurrent deliveries for one webhook
HITKEEP_WEBHOOK_MAX_ATTEMPTS 6 Total delivery attempts before failure
HITKEEP_WEBHOOK_RETRY_BASE_SECONDS 30 Initial retry delay
HITKEEP_WEBHOOK_RETRY_MAX_SECONDS 21600 Maximum retry delay
HITKEEP_WEBHOOK_RETENTION_DAYS 30 Delivery-history retention
HITKEEP_WEBHOOK_SWEEP_SECONDS 30 Pending-delivery recovery interval

Self-hosted HitKeep includes the webhook worker in the same binary as the dashboard, queue, and database. If your team wants signed event delivery without owning upgrades, backups, and worker operations, start a managed HitKeep Cloud deployment.