API
Webhooks
Signed events and how to check the signature.
Set an https URL on the key. Hawkeye POSTs JSON {"type": "<event>", "data": { ... }} with a 5 second timeout. There is no retry loop. A failure is logged as a status line, without the body and without the secret.
Headers
Content-Type: application/jsonUser-Agent: Hawkeye-PartnerX-Hawkeye-Eventis the event name.X-Hawkeye-Signatureissha256=plus the hex HMAC-SHA256 of the raw body, keyed with the signing secret.
Events the server sends
| Event | When | data fields |
|---|---|---|
message.completed | A partner message finishes | conversation_id, agent_id, message, credits |
call.started | An outbound or inbound call is accepted | call, and agent_id on an outbound call |
call.transcript | One utterance is bridged | call_id, text |
call.ended | A call is ended | call_id, summary |
usage.recorded | An image generation is charged | kind (image), credits, agent_id |
Video generation charges credits and does not emit usage.recorded in the current handler.
Check the signature
digest=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$HAWKEYE_WEBHOOK_SECRET" | awk '{print $NF}')
test "sha256=$digest" = "$HAWKEYE_SIGNATURE" && echo okconst crypto = require("crypto");
function verify(body, header) {
const expected = "sha256=" + crypto.createHmac("sha256", process.env.HAWKEYE_WEBHOOK_SECRET)
.update(body)
.digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(String(header));
return a.length === b.length && crypto.timingSafeEqual(a, b);
}import hashlib, hmac, os
def verify(body: bytes, header: str) -> bool:
secret = os.environ["HAWKEYE_WEBHOOK_SECRET"].encode()
expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)Compare the header to the digest of the raw bytes, before you parse JSON. The signing secret is the whsec_ value shown once when you set the URL.