Webhooks
Events
| Event | Trigger |
|---|---|
meal.created | New meal logged |
meal.updated | Meal edited |
gut_score.created | New daily gut score |
overnight_score.created | New overnight score |
digestion.state_changed | State transition |
windows.updated | Windows recalculated |
Subscribe
curl -X POST "https://suna.health/api/v1/webhooks" \
-H "Authorization: Bearer suna_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{"url":"https://yourapp.com/webhook","events":["meal.created"],"secret":"your_secret_16chars"}'Verify Signatures
Every webhook carries X-Suna-Signature with timestamp and HMAC:
X-Suna-Signature: t=1710590460,v1=abc123def456...Signed payload = timestamp + "." + body. Reject timestamps older than 5 minutes to prevent replay attacks.
Python
import hmac, hashlib, time
def verify(payload, sig_header, secret, tolerance=300):
parts = dict(p.split("=", 1) for p in sig_header.split(","))
ts = int(parts["t"])
if abs(time.time() - ts) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{ts}.{payload}".encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])TypeScript
import { createHmac, timingSafeEqual } from "crypto";
function verify(payload: string, sigHeader: string, secret: string): boolean {
const parts: Record<string, string> = {};
sigHeader.split(",").forEach(p => { const [k, ...v] = p.split("="); parts[k] = v.join("="); });
const ts = parseInt(parts.t);
if (Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = createHmac("sha256", secret).update(ts + "." + payload).digest("hex");
return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(parts.v1, "hex"));
}Test Your Webhook
curl -X POST "https://suna.health/api/v1/webhooks/SUBSCRIPTION_ID/test" \
-H "Authorization: Bearer suna_sk_live_xxx"Sends a test.ping event. Returns delivered: true if your endpoint responded with 2xx.
Retry Policy
After the initial delivery attempt, up to 5 retries with exponential backoff (1s, 2s, 4s, 8s, 16s) — 6 total attempts over ~31 seconds. 10s timeout per attempt. After all retries fail, delivery stops. Deduplicate via webhook_delivery_id. Reject signatures with timestamps older than 5 minutes.