Documentation Index
Fetch the complete documentation index at: https://documentation.uponai.com/llms.txt
Use this file to discover all available pages before exploring further.
Use the x-retell-signature header together with your UponAI API key to verify every webhook request.
Only the API key with a webhook badge next to it can be used to verify webhooks.
You can also allowlist UponAI’s IP address: 100.20.5.228.
Verify with SDK
Use the raw request body string for verification — not JSON.stringify(req.body). Re-serializing may change whitespace or key ordering, causing verification to fail.
import { UponAI } from "uponai-sdk";
import express from "express";
const app = express();
// Raw body required for signature verification
app.use(express.raw({ type: "application/json" }));
app.post("/webhook", (req, res) => {
const rawBody = req.body.toString("utf-8");
if (
!UponAI.verify(
rawBody,
process.env.UPONAI_API_KEY,
req.headers["x-retell-signature"],
)
) {
console.error("Invalid signature");
return res.status(401).send();
}
const { event, call } = JSON.parse(rawBody);
// process the webhook
res.status(204).send();
});
Verify Without SDK
For languages without an official SDK, verify the signature manually using HMAC-SHA256.
Signature format:
X-Retell-Signature: v={timestamp},d={hex_digest}
v — Unix timestamp in milliseconds when the webhook was sent
d — HMAC-SHA256 hex digest of raw_body + timestamp
Verification steps:
- Extract the
X-Retell-Signature header
- Parse
v (timestamp) and d (digest) using pattern v=(\d+),d=(.*)
- Confirm the timestamp is within 5 minutes of now (prevents replay attacks)
- Compute
HMAC-SHA256(raw_body + timestamp, api_key)
- Compare computed digest with
d — if they match, the webhook is authentic
func verifyWebhook(rawBody string, apiKey string, signature string) bool {
re := regexp.MustCompile(`v=(\d+),d=(.*)`)
matches := re.FindStringSubmatch(signature)
if len(matches) != 3 {
return false
}
timestamp, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return false
}
digest := matches[2]
// Reject if older than 5 minutes
now := time.Now().UnixMilli()
if math.Abs(float64(now-timestamp)) > 5*60*1000 {
return false
}
// HMAC-SHA256 with constant-time comparison
mac := hmac.New(sha256.New, []byte(apiKey))
mac.Write([]byte(rawBody + matches[1]))
expectedMAC, _ := hex.DecodeString(digest)
return hmac.Equal(mac.Sum(nil), expectedMAC)
}