Skip to main content

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.
Node
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:
  1. Extract the X-Retell-Signature header
  2. Parse v (timestamp) and d (digest) using pattern v=(\d+),d=(.*)
  3. Confirm the timestamp is within 5 minutes of now (prevents replay attacks)
  4. Compute HMAC-SHA256(raw_body + timestamp, api_key)
  5. Compare computed digest with d — if they match, the webhook is authentic
Go
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)
}