Webhooks, log export and traces
Send your team's events to your own address, move logs to your own tools, and follow one request step by step.
Webhooks#
A webhook is an https address that receives a signed POST for the events you choose, for the whole team or for one project. It is a Pro feature: the free plan has 1 webhook, Pro has 20. The address must reach a public address; private and internal addresses are refused, and redirects are not followed.
| Event | When |
|---|---|
deployment.created · deployment.ready | A deployment started; it is live |
deployment.failed · build.failed | A production deployment or a build failed |
deployment.promoted · deployment.rolled_back | A preview was promoted; production was rolled back |
alert.fired · alert.resolved | An alert fired or resolved |
budget.reached · invoice.issued | The monthly budget was reached; an invoice was issued |
member.added · member.removed | A member joined or was removed |
domain.verified · support.reply | A domain was verified; support replied |
Every request carries Webhook-Signature: t=<time>,v1=<signature>. The signature is the HMAC-SHA256 of <time>.<raw body> with the webhook's secret. Check the raw body with a constant-time comparison, and refuse a request whose t is more than 5 minutes from your clock, so it cannot be replayed. The secret is shown only when you create or rotate it.
const sig = Object.fromEntries(req.headers["webhook-signature"].split(",").map((p) => p.split("=")));
const want = crypto.createHmac("sha256", secret).update(`${sig.t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(sig.t)) < 300;
if (!fresh || !crypto.timingSafeEqual(Buffer.from(want), Buffer.from(sig.v1))) return res.writeHead(400).end();Any 2xx within 10 seconds is success. Otherwise it is retried 5 more times: after 30 seconds, 2 minutes, 10 minutes, 1 hour and 6 hours. If 5 deliveries in a row fail every attempt, the webhook is disabled and the owners and admins are told; turn it back on with webhooks enable. Each delivery stays in the log for 30 days with its request and response (truncated), and can be sent again.
cloudhost webhooks add https://example.com/hooks/deploys --events deployment.ready,deployment.failed --project shop
cloudhost webhooks test 4f3a9c2e
cloudhost webhooks deliveries 4f3a9c2e --status failed
cloudhost webhooks redeliver 4f3a9c2e 812Log export#
Send a project's or the whole team's logs to your address (Pro): https with NDJSON (one JSON object per line, gzip-compressed) or syslog over TLS (syslog+tls://host:6514). Filter by source (request, console, cron, fetch) and level (debug, info, warn, error), and sample a share of the lines. HTTPS requests are signed with Log-Signature, as webhooks are; you can add up to 5 headers such as Authorization (they are never shown again).
Batches are at most 500 lines or 1 MB. If the endpoint does not answer, sending is retried with growing gaps (5 seconds to 5 minutes) and the reading position is kept, so no line is skipped. After 30 failures in a row the export is disabled and the team is told. The health view shows the state, the line counts and the last sends.
cloudhost drains add "errors" --kind https --url https://logs.example.com/ingest --levels warn,error \
--header "Authorization:Bearer $TOKEN"
cloudhost drains add "all logs" --kind syslog --url syslog+tls://logs.example.com:6514 --project shop
cloudhost drains show 7b1d0e55Request traces#
A trace is the path of one request: the function, database queries, cache commands and fetch calls, each with its start and duration. Under Observability → Traces you see the list with filters for status, errors, slowness and path, and open one to see a waterfall of its spans. Traces are kept 24 hours on the free plan and 7 days on Pro.
Every request to an app or function gets a trace id, returned in the X-Trace-Id response header; a request with a valid W3C traceparent header continues that trace. Your code receives the request's traceparent too, to pass on in its own fetch calls if it wants. Calls through env.DB, env.CACHE, env.BUCKET and outbound fetch become spans by themselves: a query by its verb and table (SELECT cart_items), a cache call by its command, a fetch by its method and host. Values, keys, bodies, headers and query strings are never recorded.
Not every request is kept. Per project the first few are always recorded (so you see every request while trying your site), then a sample sized by the plan: about one every 10 seconds on the free plan and one a second on Pro. Every failed (5xx) or slow (over 1 s) request is kept as well, up to a limit. The log entry of a recorded request links to its trace.
cloudhost traces list --project shop --status 5xx
cloudhost traces show 0123456789abcdef0123456789abcdef