Send events via HTTP POST to a URL endpoint. Outpost supports two webhook modes:

  • Default mode — Customizable headers and signature format
  • Standard Webhooks mode — Follows the Standard Webhooks specification

Creating a Webhook Destination

curl 'https://api.outpost.hookdeck.com/2025-07-01/tenants/<TENANT_ID>/destinations' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <API_KEY>' \
--data '{
  "type": "webhook",
  "topics": ["user.created", "user.updated"],
  "config": {
    "url": "https://example.com/webhooks"
  }
}'

Configuration

Config

FieldTypeRequiredDescription
config.urlstringYesThe URL to send events to
config.custom_headersstringNoJSON object of custom HTTP headers to include

Credentials

FieldTypeRequiredDescription
credentials.secretstringNoSigning secret — auto-generated if not provided
credentials.previous_secretstringNoPrevious secret during a rotation window
credentials.previous_secret_invalid_atstringNoRFC 3339 timestamp when the previous secret expires

If secret is not provided, one is auto-generated. Tenants can trigger secret rotation but cannot set secrets directly.

Event Format

When you publish an event:

{
  "topic": "user.created",
  "data": { "user_id": "usr_123", "email": "user@example.com" },
  "metadata": { "source": "signup-service" }
}

Outpost sends an HTTP POST request:

POST /webhooks HTTP/1.1
Content-Type: application/json
x-outpost-event-id: evt_abc123
x-outpost-topic: user.created
x-outpost-timestamp: 2024-06-01T08:23:36Z
x-outpost-signature: v0=abc123def456...
x-outpost-source: signup-service

{"user_id": "usr_123", "email": "user@example.com"}

The request body contains the event's data field as JSON. The metadata field is translated to headers using the configured prefix.

Event ID header and idempotency

Webhook delivery is at-least-once (see Event delivery & retries). Your handler should deduplicate using the event id — the same stable id you set when publishing (Outpost may redeliver on retries).

In default mode, that id is sent as the system event-id metadata field. The HTTP header name is {header_prefix}{metadata-key} with no extra separator — the prefix and key are concatenated as-is. So the default prefix x-outpost- plus event-id yields X-Outpost-Event-Id; a prefix without a trailing separator (for example x-acme) would produce x-acme + event-idX-Acmeevent-Id (Go canonicalizes the wire name). Include a trailing hyphen in the prefix when you want a conventional shape like X-Acme-Event-Id. Change the prefix with DESTINATIONS_WEBHOOK_HEADER_PREFIX, or omit this header with DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER.

In Standard Webhooks mode, the same value is sent as the webhook-id header (default prefix webhook-, so typically Webhook-Id) per the Standard Webhooks specification.

Signatures

Default Mode

The signature is computed over the request body:

HMAC-SHA256(secret, "${body}")

The x-outpost-signature header value follows the format: v0=${signature}

If the destination has an unexpired previous_secret during secret rotation, Outpost includes one signature for each valid secret in the same header. The current secret's signature is first, followed by the previous secret's signature: v0=<signature-from-current-secret>,<signature-from-previous-secret>

To verify:

  1. Extract the signature header
  2. Split the v0= value on commas
  3. Compute the expected signature using each active secret you accept
  4. Accept the request if any signature matches using a constant-time comparison
  5. Optionally reject requests with old timestamps to prevent replay attacks

Custom signature templates

In default mode, operators can customize both the signed content and the signature header value:

  • DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE controls the string passed to HMAC.
  • DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE controls the value written to the signature header.

These use the same configuration key names in both deployment models, but they are applied differently: Managed Outpost stores them through the Config API or Hookdeck dashboard, while self-hosted Outpost reads them from environment variables or YAML configuration.

Templates use Go template syntax with helper functions such as join. The signature content template can use:

FieldDescription
.EventIDEvent id used for the delivery
.TopicEvent topic
.TimestampDelivery timestamp
.BodyRaw request body

The signature header template can use the same metadata plus .Signatures, which is the list of generated signatures for all valid secrets. During secret rotation, .Signatures contains the current secret's signature first and the previous secret's signature second.

For example, to include a Unix timestamp in the signed content and header:

Set the values in the Config API or in Hookdeck Destinations settings. For the Config API, send the configuration keys in the JSON body:

{
  "DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE": "{{.Timestamp.Unix}}.{{.Body}}",
  "DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE": "t={{.Timestamp.Unix}},v0={{.Signatures | join \",\"}}"
}

Set the values as environment variables:

DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE="{{.Timestamp.Unix}}.{{.Body}}"
DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE="t={{.Timestamp.Unix}},v0={{.Signatures | join \",\"}}"

You can also set signature_content_template and signature_header_template in YAML under destinations.webhook.

With one secret, the header looks like:

x-outpost-signature: t=1717249416,v0=<signature-from-current-secret>

During rotation, the same template preserves both signatures:

x-outpost-signature: t=1717249416,v0=<signature-from-current-secret>,<signature-from-previous-secret>

If you customize DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE, keep .Signatures in the output so receivers can verify requests during secret rotation.

Standard Webhooks Mode

Follows the Standard Webhooks specification:

base64(HMAC-SHA256(secret, "${webhook-id}.${timestamp}.${body}"))

Use the official Standard Webhooks SDK to verify signatures. Secrets use the whsec_<base64> format.

During secret rotation, Outpost includes one Standard Webhooks signature entry per valid secret in the webhook-signature header. The current secret's signature is first, followed by the previous secret's signature:

webhook-signature: v1,<signature-from-current-secret> v1,<signature-from-previous-secret>

Enable Standard Webhooks mode by setting DESTINATIONS_WEBHOOK_MODE=standard in the Config API or in Hookdeck Destinations settings.

Enable Standard Webhooks mode:

DESTINATIONS_WEBHOOK_MODE=standard

Secret Rotation

Rotate a webhook secret without downtime. During the rotation window, both the old and new secrets produce valid signatures.

curl --request PATCH \
'https://api.outpost.hookdeck.com/2025-07-01/tenants/<TENANT_ID>/destinations/<DESTINATION_ID>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <API_KEY>' \
--data '{
  "credentials": {
    "rotate_secret": "true",
    "previous_secret_invalid_at": "2025-01-15T00:00:00Z"
  }
}'

When rotation is triggered:

  1. The current secret becomes previous_secret
  2. A new secret is generated
  3. The previous secret remains valid until previous_secret_invalid_at (default: 24 hours)
  4. During the rotation window, the signature header contains signatures generated with both valid secrets
  5. After previous_secret_invalid_at, the previous secret is no longer included and the signature header returns to a single signature

Signature header format depends on the webhook mode. With the default header prefix:

ModeHeaderDuring rotation
Defaultx-outpost-signaturev0=<signature-from-current-secret>,<signature-from-previous-secret>
Standard Webhookswebhook-signaturev1,<signature-from-current-secret> v1,<signature-from-previous-secret>

Receivers should treat rotation as an allow-list period: verify the request against the current secret and the previous secret, and accept the request if any signature in the header matches one of those secrets. Outpost signs with the current secret first.

Custom Headers

Tenants can add custom HTTP headers to webhook requests for authentication or routing:

curl 'https://api.outpost.hookdeck.com/2025-07-01/tenants/<TENANT_ID>/destinations' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <API_KEY>' \
--data '{
  "type": "webhook",
  "topics": ["*"],
  "config": {
    "url": "https://example.com/webhooks",
    "custom_headers": "{\"x-api-key\": \"secret123\"}"
  }
}'

Header names must start with a letter or digit and may contain letters, digits, underscores, and hyphens. The following headers cannot be overridden: content-type, content-length, host, connection, user-agent.

Custom webhook headers in the tenant portal are disabled by default. Enable them in Hookdeck User Portal settings.

Custom headers are disabled in the tenant portal by default. Enable with:

PORTAL_ENABLE_WEBHOOK_CUSTOM_HEADERS=true

Operator Configuration

Configure webhook operator behavior using these keys in the Config API or in Hookdeck Destinations settings: DESTINATIONS_WEBHOOK_HEADER_PREFIX, DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADER, DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADER, DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADER, DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADER, DESTINATIONS_WEBHOOK_MODE, DESTINATIONS_WEBHOOK_SIGNATURE_ALGORITHM, DESTINATIONS_WEBHOOK_SIGNATURE_ENCODING, DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE, and DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATE.

Header Settings

VariableDefaultDescription
DESTINATIONS_WEBHOOK_HEADER_PREFIXx-outpost- / webhook-Prefix for system webhook headers (event id, topic, timestamp, signature). Unless overridden, defaults to x-outpost- when DESTINATIONS_WEBHOOK_MODE is default and webhook- when standard.
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_EVENT_ID_HEADERfalseDisable the event ID header
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TIMESTAMP_HEADERfalseDisable the timestamp header
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_TOPIC_HEADERfalseDisable the topic header
DESTINATIONS_WEBHOOK_DISABLE_DEFAULT_SIGNATURE_HEADERfalseDisable the signature header

Signature Settings

VariableDefaultDescription
DESTINATIONS_WEBHOOK_MODEdefaultSet to standard for Standard Webhooks compliance
DESTINATIONS_WEBHOOK_SIGNATURE_ALGORITHMhmac-sha256Signature algorithm
DESTINATIONS_WEBHOOK_SIGNATURE_ENCODINGhexEncoding: hex or base64
DESTINATIONS_WEBHOOK_SIGNATURE_CONTENT_TEMPLATE{{.Body}}Template for signed content in default mode
DESTINATIONS_WEBHOOK_SIGNATURE_HEADER_TEMPLATEv0={{.Signatures | join ","}}Template for signature header value in default mode