Reliability
OpenTelemetry export
Send one span per delivery attempt and delivery success-rate and latency metrics to any OTLP/HTTP backend, without touching the delivery path.
Hooks can export delivery traces and metrics to any OpenTelemetry collector that speaks OTLP/HTTP JSON — Honeycomb, Grafana Alloy or the OpenTelemetry Collector, for example. Export is configured per workspace and is best effort: a collector outage never delays, retries or fails a webhook delivery.
Configure
Section titled “Configure”Set Workspace → Delivery → Trace & metric export in the console, or PATCH the project:
PATCH /v1/projectContent-Type: application/json
{ "otelEndpoint": "https://api.honeycomb.io", "otelHeaders": [ { "name": "x-honeycomb-team", "value": "{{secret.HONEYCOMB_API_KEY}}" } ]}| Field | Description |
|---|---|
otelEndpoint |
Base OTLP/HTTP URL. Hooks appends /v1/traces and /v1/metrics. Empty clears export. |
otelHeaders |
Up to 20 headers sent with every export. Values may reference a secret as {{secret.NAME}}. |
A pasted signal path (/v1/traces) is stripped, so either form works. Header values may use {{secret.NAME}}; the referenced project secret must exist when you save, and the value is decrypted only at export time. Literal header values are stored in plain text, so put credentials in secrets.
Saving requires the workspace owner or an admin session. The endpoint must be http or https.
What is exported
Section titled “What is exported”Traces
Section titled “Traces”One span per settled delivery attempt:
| Span field | Value |
|---|---|
| Name | webhook.delivery.attempt |
| Kind | CLIENT |
traceId |
Stable per event: retries of the same event share one trace id |
spanId |
Stable per delivery attempt |
| Attributes | hooks.delivery_id, hooks.event_id, hooks.endpoint_id, hooks.destination_id, hooks.attempt, hooks.outcome, http.response.status_code, hooks.error_code |
| Status | OK on success, ERROR on failed/dead, UNSET for retry/filtered |
| Events | One per lifecycle stage (stage.action) with status and duration |
hooks.outcome is one of succeeded, retry, failed, dead or filtered.
Metrics
Section titled “Metrics”One export per workspace per queue batch, emitted after the batch is acknowledged:
| Metric | Type | Notes |
|---|---|---|
hooks.delivery.attempts |
Sum | Settled attempts, split by the hooks.outcome attribute |
hooks.delivery.duration |
Histogram | Duration in ms, bounds 50 ms … 30 s |
hooks.delivery.success_ratio |
Gauge | succeeded / all settled attempts since the exporter started |
The resource carries service.name = hooks-delivery and hooks.project_id, so a backend can group by workspace.
Counters and histograms use cumulative temporality: each export repeats the running total for the worker instance rather than a per-batch delta, because Prometheus’ OTLP receiver discards delta sums and histograms. A worker restart starts a new series at zero, which Prometheus records as a counter reset — values never jump backwards within a running instance. success_ratio therefore reflects the lifetime of the worker instance, not a single batch.
Privacy
Section titled “Privacy”Only ids, timings, status codes, error codes and lifecycle stage names are exported. Payload bodies, destination URLs, request headers (except the configured OTLP headers) and secret values never leave the workspace.
Delivery guarantees
Section titled “Delivery guarantees”Observations are collected in memory during one queue batch and exported after every message in the batch is acknowledged. If the collector is unreachable, times out (5 s) or rejects the request, Hooks logs a single otel.export_failed line containing the workspace id, the collector host and the reason, then moves on. No delivery is retried or delayed because of export.
Private collectors
Section titled “Private collectors”The OTLP endpoint passes through the same SSRF guard as outbound destinations, so a workspace cannot point export at internal services. Self-hosted deployments that run a collector next to the Worker set OTEL_ALLOW_PRIVATE_ENDPOINTS=true in the deployment environment; the default is false.
Verify
Section titled “Verify”- Save an endpoint that points at your collector.
- Send a test event to an endpoint with a destination.
- In the backend, filter on
service.name = hooks-delivery: each attempt appears as awebhook.delivery.attemptspan, andhooks.delivery.*metrics arrive within the next queue batch.
Sending to a collector that does not exist is safe: the delivery still succeeds and the console delivery log is unchanged.