VI
01/16/2026, 1:57 PMAI Evy
01/16/2026, 1:57 PMAI Evy
01/16/2026, 1:58 PMX-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:
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) :
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) ]()AI Evy
01/16/2026, 1:58 PMAI Evy
01/16/2026, 1:58 PMVI
01/16/2026, 8:46 PMAI Evy
01/16/2026, 8:46 PMAI Evy
01/16/2026, 8:46 PMv=<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) :
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) ]()AI Evy
01/16/2026, 8:46 PMAI Evy
01/16/2026, 8:46 PMVI
01/16/2026, 8:47 PMAI Evy
01/16/2026, 8:47 PMAI Evy
01/16/2026, 8:47 PMv=<timestamp>,d=<digest> with the signed payload being {timestamp}.{raw_body}) , here's an approach: [(2) ]()
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.AI Evy
01/16/2026, 8:47 PMAI Evy
01/16/2026, 8:47 PM