Outbound Signing

Sign outbound webhook deliveries with HMAC for receiver verification.

Outbound signing mirrors inbound signature verification but from the other side: hookstream signs every outbound delivery so your receivers can verify it came from you, not from an attacker who guessed the URL.

How it works

1

You configure a secret on the destination

Stored in signing_config on the destination.

2

Hookstream computes HMAC(secret, body)

For every outbound request, using the configured algorithm (SHA-256 or SHA-1).

3

The signature is sent in a header

Default header is X-hookstream-Signature with a sha256= prefix.

4

Your receiver recomputes and compares

If the signatures match (constant-time compare), the request is verified.

Configure outbound signing

Set signing_config on an HTTP destination:

bash
curl -X PATCH https://hookstream.io/v1/destinations/dest_xyz \ -H "X-API-Key: $HOOKSTREAM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "signing_config": { "secret": "your_secret_here", "algorithm": "sha256", "header": "X-hookstream-Signature", "prefix": "sha256=", "include_timestamp": false, "timestamp_header": "X-hookstream-Timestamp" } }'

Fields

FieldDefaultNotes
secretRequired. The shared secret.
algorithmsha256sha256 (recommended) or sha1.
headerX-hookstream-SignatureHeader the signature lands in.
prefix"{algorithm}="Prepended to the hex digest. Set "" to send raw hex.
include_timestampfalseWhen true, the signing payload becomes {unix_timestamp}.{body} (dot-separated) and the timestamp is also sent in timestamp_header.
timestamp_headerX-hookstream-TimestampOnly used when include_timestamp is true.

The verification examples below assume include_timestamp: false. If you enable timestamps, the receiver must read X-hookstream-Timestamp, reconstruct the signing payload as `${timestamp}.${rawBody}`, and use that string instead of rawBody when computing the HMAC.

Use SHA-256. SHA-1 is only there for compatibility with older receivers — new integrations should never pick it.

Verify the signature on the receiver side

const crypto = require("crypto");

function verifyHookstream(req, rawBody) {
  const signature = req.headers["x-hookstream-signature"] ?? "";
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.HOOKSTREAM_SECRET)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Use a constant-time comparison (crypto.timingSafeEqual, hmac.compare_digest, hmac.Equal) — not a plain == — so an attacker can't learn the signature byte-by-byte via timing side channels.

Next Steps

Destinations API

Full destination schema, including signing_config.

Learn More
Signature Verification

The inbound side — verifying signatures on the events you receive.

Learn More
Ask a question... ⌘I