https://retellai.com/?utm_source=discord&utm_medium=seo logo
Can you confirm the exact format and signing algor...
# ❓・ai-evy-support
v
@AI Evy Can you confirm the exact format and signing algorithm for the x-retell-signature header — specifically whether it’s a compound value (e.g., includes t=/v1= fields) and whether the HMAC is computed over the raw request body bytes or JSON.stringify(parsedBody) (and if a timestamp is included in the signed payload, what the exact concatenation format is)?
a
Thanks for tagging me @VI. I'll look through relevant sources and get back to you shortly.
### Answer The
X-Retell-Signature
header uses a compound format with timestamp and digest fields:
v=<timestamp>,d=<digest>
[(1) ]() Signing Details: - **Timestamp (
v
) **: In milliseconds - **Digest (
d
) **: HMAC-SHA256 hex digest - **Signed payload format**:
{timestamp}.{raw_body}
— the timestamp concatenated with a period, then the raw request body Critical: Use raw request body bytes You must verify using the raw request body (exact bytes as received) , not a parsed/re-stringified JSON object. Re-serializing can change number formatting (e.g., exponential notation like
4.0000000126383384e-05
) , which breaks signature verification. [(2) ]() Python example:
Copy code
python
raw_body = (await request.body() ) .decode("utf-8")
valid_signature = retell.verify(
    raw_body,
    api_key=os.environ["RETELL_API_KEY"],
    signature=request.headers.get("X-Retell-Signature") ,
)
Node.js example (from official docs) :
Copy code
javascript
import { Retell } from "retell-sdk";

Retell.verify(
  JSON.stringify(req.body) ,
  process.env.RETELL_API_KEY,
  req.headers["x-retell-signature"] as string,
)
[(3) ]() Important notes: - Only the API key marked with a webhook badge can verify signatures , [(1) ]() - Use the full API key — don't remove the "key_" prefix - You can also allowlist Retell's IP address:
100.20.5.228
[(4) ]() , [(3) ]()
​ To learn more, see the sources I considered: [[1] Webhook Verification](https://discord.com/channels/1186424376300679209/1400483295082451026) [[2] Webhook signature verification failing with exponential-notation numbers](https://discord.com/channels/1186424376300679209/1456014571578986621) [[3] Secure the webhook](https://docs.retellai.com/features/secure-webhook) [[4] Custom Function](https://docs.retellai.com/build/conversation-flow/custom-function) ​
Feel free to tag @AI Evy with additional questions. I also appreciate feedback, it helps me improve.
v
so how to perform this without using the SDK?
a
Thanks for the follow-up, @VI. I'll get back to you soon.
### Answer Based on the available sources, here's how to verify the webhook signature without using the SDK: Signature format:
v=<timestamp>,d=<digest>
[(1) ]() Algorithm: HMAC-SHA256 Signed payload:
{timestamp}.{raw_body}
— timestamp concatenated with a period, then the raw request body Ruby example (from community discussion) :
Copy code
ruby
signature_header = request.headers["X-Retell-Signature"].to_s
timestamp = signature_header[/v=(\d+) /, 1]
provided_digest = signature_header[/d=([a-f0-9]+) /i, 1]

# timestamp is in milliseconds
if timestamp.to_i < (Time.now.to_i * 1000 - 5.minutes.to_i * 1000)
  # Stale timestamp - reject
end

# Use the full webhook API key (don't remove "key_" prefix)
secret = Rails.application.credentials.dig(:retell_ai, :webhook_apikey)

raw_body = request.raw_post
message = "#{timestamp}.#{raw_body}"
expected_digest = OpenSSL::HMAC.hexdigest("sha256", secret, message)

# Compare digests
ActiveSupport::SecurityUtils.secure_compare(expected_digest, provided_digest)
Key points: - Use the raw request body bytes — do not parse and re-stringify JSON - Use the full API key with the webhook badge — don't remove the "key_" prefix - Timestamp is in milliseconds - You can also allowlist Retell's IP address:
100.20.5.228
[(2) ]() , [(3) ]() Note: The official documentation only provides SDK examples (Node.js and Python) for signature verification. The manual implementation above is from community discussion and may require adjustment for your specific language/framework. [(1) ]()
​ To learn more, see the sources I considered: [[1] Webhook Verification](https://discord.com/channels/1186424376300679209/1400483295082451026) [[2] Custom Function](https://docs.retellai.com/build/conversation-flow/custom-function) [[3] Secure the webhook](https://docs.retellai.com/features/secure-webhook) ​
Feel free to tag @AI Evy with additional questions. I also appreciate feedback, it helps me improve.
v
can you give me an example in js?
a
Thanks for the follow-up, @VI. I'll get back to you soon.
### Answer The official documentation only provides a JavaScript example using the SDK. [(1) ]() For manual verification without the SDK in JavaScript, based on the signature format from community discussions (
v=<timestamp>,d=<digest>
with the signed payload being
{timestamp}.{raw_body}
) , here's an approach: [(2) ]()
Copy code
javascript
import crypto from 'crypto';

function verifyRetellWebhook(rawBody, signature, apiKey) {
  const timestamp = signature.match(/v=(\d+) /) ?.[1];
  const providedDigest = signature.match(/d=([a-f0-9]+) /i) ?.[1];
  
  if (!timestamp || !providedDigest) {
    return false;
  }
  
  // Check for stale timestamp (5 minute window)
  const now = Date.now() ;
  if (parseInt(timestamp) < now - 5 * 60 * 1000) {
    return false;
  }
  
  const message = `${timestamp}.${rawBody}`;
  const expectedDigest = crypto
    .createHmac('sha256', apiKey)
    .update(message)
    .digest('hex') ;
  
  return crypto.timingSafeEqual(
    Buffer.from(expectedDigest) ,
    Buffer.from(providedDigest)
  ) ;
}
Important: - Use the raw request body exactly as received — do not parse and re-stringify [(3) ]() - Use the API key marked with the webhook badge [(1) ]() - You can also allowlist Retell's IP address:
100.20.5.228
[(4) ]() , [(1) ]() Note: This manual implementation is derived from community discussion patterns. The official documentation recommends using the SDK's
Retell.verify()
function.
​ To learn more, see the sources I considered: [[1] Secure the webhook](https://docs.retellai.com/features/secure-webhook) [[2] Webhook Verification](https://discord.com/channels/1186424376300679209/1400483295082451026) [[3] Webhook verification failure](https://discord.com/channels/1186424376300679209/1359570824461418627) [[4] Custom Function](https://docs.retellai.com/build/conversation-flow/custom-function) ​
Feel free to tag @AI Evy with additional questions. I also appreciate feedback, it helps me improve.