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.
Verify A Delivery In Go
Section titled “Verify A Delivery In Go”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.
Create A Webhook
Section titled “Create A Webhook”Webhook administration is a human dashboard workflow. API clients cannot create or rotate webhooks.
- Open Integration -> Webhooks.
- Select Site for one site’s events or Instance for cross-site and administrative events.
- Choose Create webhook, enter the HTTPS destination, and select at least one event.
- Save the signing secret immediately. HitKeep shows it only after creation or rotation.
- Use Send test and confirm that the receiver returns a
2xxresponse. - Open Deliveries to inspect status, attempt count, response code, and retry outcomes.


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.
Request Headers And Signature
Section titled “Request Headers And Signature”| 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.
Payload Contract
Section titled “Payload Contract”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.
Available Events
Section titled “Available Events”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.
Delivery And Retry Behavior
Section titled “Delivery And Retry Behavior”| 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:
- Verify the timestamp and signature against the exact body.
- Check whether the event or delivery ID was already processed.
- Apply the change idempotently in a transaction or durable queue.
- Persist the processed ID.
- Return
2xxonly 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.

Destination Safety
Section titled “Destination Safety”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.
Rotate Or Revoke A Secret
Section titled “Rotate Or Revoke A Secret”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.
Outbound Webhook Configuration
Section titled “Outbound Webhook Configuration”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 |
Related Documentation
Section titled “Related Documentation”- HitKeep REST API reference for the generated webhook schemas and dashboard-session endpoints
- Runtime facts and product limits for the canonical webhook security and delivery boundary
- HitKeep roles and permissions for site and instance administration
- Configuration reference for every flag and environment variable
- Server-side event tracking when another system needs to send events into HitKeep instead
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.