Webhooks
Receive every logged request, with its cost and tokens, at an HTTPS endpoint of your own, signed so you can check it came from CoreValue.
A webhook sends each request the gateway logs to an HTTPS endpoint you run: the request and response bodies and, if you ask for them, the model, provider, cost, tokens and latency. Use it to feed your own warehouse, billing or alerting.
Webhooks carry request logs. Budget alerts are sent by email and to Slack; see Budget alerts.
Add a webhook
You need the Owner or Admin role.
-
Open Notifications, then the Webhooks tab.
-
Enter the Endpoint URL. It must be
https://. Private, internal and cloud-metadata addresses are refused. -
Optionally set:
Field What it does Sample Rate The percentage of requests to send, from 0 to 100. Default 100. Properties Filters Only send requests whose properties match. Each filter is a key and an exact value, and a request must match all of them. Include Enhanced Data Adds the model, provider, cost, tokens, latency and a link to the full request and response. Only an Owner can turn this on. -
Save. The webhook's HMAC Key is shown in the list, hidden until you reveal it. Copy it: it is the secret you verify deliveries with.
Use Test on the webhook to send a sample delivery with made-up data.
What you receive
Each delivery is a POST with Content-Type: application/json:
{
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"request_body": "{\"model\":\"gpt-4o-mini\",\"messages\":[…]}",
"response_body": "{\"id\":\"chatcmpl-…\",\"choices\":[…]}",
"user_id": "user_abc123",
"model": "gpt-4o-mini",
"provider": "openai",
"request_response_url": "https://…",
"metadata": {
"cost": 0.0015,
"promptTokens": 120,
"completionTokens": 48,
"totalTokens": 168,
"latencyMs": 812
}
}| Field | Notes |
|---|---|
request_id | The request's Cova-Id. |
request_body, response_body | The bodies as strings. A body over 10 KB is replaced with a short message; fetch the full one from request_response_url or the console. |
user_id | Present when the request carried Cova-User-Id. |
model, provider, metadata, request_response_url | Only with Include Enhanced Data. metadata.cost is in US dollars. request_response_url is a signed link that expires. |
Verify the signature
Every delivery carries a Cova-Signature header: the HMAC-SHA256 of the raw
request body, keyed with the webhook's HMAC Key, as lowercase hex. Compute it over
the body exactly as received, before parsing the JSON, and compare in constant
time.
import { createHmac, timingSafeEqual } from "node:crypto";
function isFromCoreValue(rawBody: string, signature: string, secret: string) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
return (
signature.length === expected.length &&
timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
);
}import hashlib
import hmac
def is_from_corevalue(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature, expected)The signature covers the body only, with no timestamp, so it proves a delivery
came from CoreValue but not when. Use request_id to discard a delivery you
have already processed.
Delivery
- Timing. A delivery is sent about 30 seconds after the request is logged.
- Success. Any
2xxresponse. Respond within 90 seconds. - No retries. A delivery that fails or times out is not sent again. If you must not miss a request, reconcile against the request log, which records a request whether or not its delivery succeeded.
- Order. Deliveries are not guaranteed to arrive in the order the requests were made.