Webhooks

Verify webhook signatures

Check the X-Studio-Signature header so you only trust requests we sent.

Oct 11, 20261 min read

Every request to a custom endpoint is signed with your endpoint's secret. Check the signature before you trust the body.

How it's signed

  • The signature is an HMAC-SHA256 of the raw request body, keyed with your signing secret.
  • It is sent as X-Studio-Signature: sha256=<hex digest>.
  • Secrets start with whsec_. Find yours under Signing secret on the endpoint page.

Node.js

import { createHmac, timingSafeEqual } from "node:crypto"

export function verify(rawBody: string, header: string, secret: string) {
  const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`
  const a = Buffer.from(header)
  const b = Buffer.from(expected)
  return a.length === b.length && timingSafeEqual(a, b)
}

Python

import hashlib, hmac

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(header, expected)

Common mistakes

  • Hash the exact bytes you received. Parsing the JSON and serialising it again changes the bytes, so the signature won't match.
  • Use a constant-time comparison, as in the examples above, instead of ==.
  • After Rotate secret, update your server straight away. The old secret stops working.

Chat-app destinations such as Slack receive a formatted message instead, and are not signed.

Was this helpful?
Ship on-brand posts in minutes

AI content, scheduling, engagement and a full marketing suite — free to start.

Start free
Back to documentation

Keep reading

Put this into practice in AeroReply

Every tool lives in the same account, from the free plan up.

Put this into practice

Start free — design, schedule and grow from one workspace.

  • No credit card
  • 2-minute setup
  • Cancel anytime