Introducing the Ochre AI support workspace. Start a 14-day trial

How Ochre verifies inbound webhooks

HMAC-SHA256, constant-time comparison, and replay protection (timestamp windows + GitHub delivery-id dedupe) for Resend, Slack, HubSpot, Linear, GitHub, and Stripe.

By ChristopherUpdated 3 min read

Webhooks are how Ochre learns about events from other tools: a customer paid an invoice, an email reply landed, a Linear issue was closed, a GitHub PR was merged. Webhooks are also how attackers get into systems that trust them by default. Ochre does not.

What we verify

Every inbound webhook to Ochre passes through a verification step before any business logic runs. The exact algorithm depends on the sender, but the rules are the same:

  1. The request must carry a signature header.
  2. The signature must match an HMAC computed from the raw body and a shared secret.
  3. The comparison must be constant-time (crypto.timingSafeEqual).
  4. The request must be recent, or carry a non-replayed identifier.

If any step fails, the request is rejected with 401 (or 400 for malformed headers) and never reaches the conversation, customer, or routing layer.

The six integrations and how they sign

Resend (inbound email, via Svix)

Inbound email webhooks from Resend are signed by Svix. Svix sends three headers: svix-id, svix-timestamp, and svix-signature. Ochre computes HMAC-SHA256 over <id>.<timestamp>.<body>, accepts any of the comma-separated signatures (Svix rotates), enforces a 5-minute timestamp window, and rejects already-seen IDs.

Slack

Slack signs requests with v0= prefixed HMAC-SHA256 over v0:<timestamp>:<raw body>, with the secret from your Slack app. Ochre re-computes that string, compares with crypto.timingSafeEqual, and rejects timestamps older than 5 minutes.

HubSpot

HubSpot uses HMAC-SHA256 with the request method, URI, body, and timestamp concatenated into a canonical string. Same constant-time check. Same 5-minute timestamp window.

Linear

Linear signs with HMAC-SHA256 of the raw body using your webhook secret. The signature header is linear-signature. The Linear timestamp header is required — requests without a timestamp, or with one outside a 5-minute window, are rejected. Same constant-time comparison. We also pin the workspace ID in the payload to your stored Linear org so a leaked secret cannot push events into a different workspace.

GitHub

GitHub signs with HMAC-SHA256 in the x-hub-signature-256 header. Constant-time comparison. GitHub does not include a verifiable timestamp, so replay protection uses delivery-id dedupe: every delivery has a unique x-github-delivery UUID, and Ochre rejects any UUID it has already processed.

Stripe

Stripe is the exception. Instead of rolling our own check, we use Stripe's official Node SDK verifier (stripe.webhooks.constructEvent) with the endpoint secret you set in the dashboard. Stripe enforces the timestamp window itself.

Why constant-time comparison matters

If you compare two HMAC strings with ==, the comparison can short-circuit on the first byte that differs. An attacker who can measure response times can bisect their way to a valid signature. crypto.timingSafeEqual runs in constant time regardless of where the strings differ.

Why replay protection matters

Even with a valid signature, an attacker who captures a webhook payload could replay it later. Two strategies:

  • Timestamp window (Resend, Slack, HubSpot, Linear, Stripe). The signed payload includes a timestamp; we reject anything outside a 5-minute window.
  • Delivery-id dedupe (GitHub). The provider includes a unique delivery UUID; we record processed IDs and reject duplicates.

What if a webhook arrives unsigned?

Rejected. Ochre does not accept unsigned webhooks from any of these integrations. If you set up a webhook and it stops arriving, the most common cause is that the secret was rotated on the sender side and not on Ochre. Reconnect from settings.

What about webhooks Ochre sends?

Ochre signs outbound webhooks the same way: HMAC-SHA256 of the raw body, with a per-endpoint secret you can copy from the integration settings. Verify on your side with constant-time comparison.

Survey tokens

CSAT survey response links are signed with HMAC using a dedicated OCHRE_SURVEY_TOKEN_SECRET, separate from the database service role key. A leaked service role key cannot forge survey responses.

Troubleshooting

If you are seeing repeated 401s in your sender's logs, check:

  • Clock skew. Both sides need to be within 5 minutes (timestamp-based providers).
  • Secret rotation on either side without updating the other.
  • A proxy or middleware that modifies the raw body before it reaches Ochre. Webhooks must be verified against the byte-exact body.

Was this article helpful?

How Ochre verifies inbound webhooks · Ochre