Stream logs to an HTTPS endpoint
An HTTPS endpoint log stream posts the requests of a gateway environment to a URL you own, in batches, about 30 seconds after each request ends. Each batch is signed so your receiver can check that it came from Faustine.
Before you begin
- An endpoint that accepts
POSToverhttps://on a public address. Faustine does not send to private addresses and does not follow redirects. - The endpoint answers with a
2xxstatus within 15 seconds. Any other answer counts as a failure and the batch is retried.
Add the stream
- In the portal, go to Agent Gateway and select your gateway.
- Select the Logs tab, then Log settings.
- Under Log streams, select Add a stream.
- Under Destination, select HTTPS endpoint.
- Fill in the fields in the table.
- Select Send test event. Faustine posts one sample request marked
test: trueand shows the status your endpoint answered. - Select Continue.
- Under What to send, choose All requests or Errors and denials, and turn on Discovery calls or Payloads if you want them. Select Continue.
- Under Review and create, enter a Name and select Create stream.
- On the stream's page, copy the Signing secret and store it where your receiver can read it, then select I saved it.
| Field | Value |
|---|---|
| URL | The address of your endpoint. Put credentials in a header, not in the URL. |
| Body | NDJSON, one request per line, or JSON array, the batch as an array. |
| Compress with gzip | Sends the body with Content-Encoding: gzip. Turn it on only if your receiver decompresses. Off by default. |
| Headers | Headers sent with every batch, such as Authorization. Optional, up to 10. The values are stored encrypted. |
Warning
The signing secret is shown once. If you lose it, select Roll signing secret under More actions on the stream's page. Deliveries are signed with the new secret immediately, and your receiver rejects them until it has it.
What your endpoint receives
Each batch is one POST of up to 500 requests and 4 MB.
| Header | Value |
|---|---|
Content-Type |
application/x-ndjson or application/json, by the Body field. |
X-Faustine-Delivery |
An ID for the batch. |
X-Faustine-Signature |
t=<unix seconds>,v1=<hex>. |
User-Agent |
Faustine-Log-Streams/1 |
Each request is one JSON document:
{
"schema": "faustine.gateway.request/v1",
"id": "req_…",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"startedAt": "2026-11-11T14:03:07.120Z",
"endedAt": "2026-11-11T14:03:07.532Z",
"durationMs": 412,
"gateway": { "id": "…", "slug": "crm", "name": "CRM" },
"env": { "id": "…", "slug": "production", "name": "Production" },
"status": "ok",
"channel": "mcp",
"kind": "operation",
"tool": "crm.deals.get",
"opKey": "crm.deals.get",
"actor": { "kind": "key", "id": "…", "name": "Nightly sync" },
"client": { "hint": "claude-code", "name": "claude-code", "version": "2.1.0" },
"calls": [{ "id": "…", "opKey": "crm.deals.get", "status": "ok", "upstreamStatus": 200, "latencyMs": 405 }],
"attempts": [{ "id": "…", "method": "GET", "url": "https://api.example.com/deals/42", "status": 200, "durationMs": 388 }]
}The example leaves out the fields that are null. For every field, see Log streams.
Delivery is at least once. A batch can arrive twice after a failure, so deduplicate by the id of each request.
Verify the signature
v1 is the HMAC-SHA256 of <t>.<body> with the stream's signing secret, in hexadecimal. <body> is the body before gzip, so decompress first if Compress with gzip is on. Reject a signature whose t is more than a few minutes old.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
if (!t || !v1) return false;
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
if (expected.length !== v1.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}Pass the body exactly as received, before any JSON parsing, and the value of the X-Faustine-Signature header.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| The form shows "Use an https:// URL" | The URL starts with http://. |
Serve the endpoint over TLS. |
| The test shows "The URL redirects" | The endpoint answers with a 3xx status. |
Enter the final URL. |
| The form shows that a header "is set by the gateway" | The header is one Faustine sets itself, such as Content-Type or X-Faustine-Signature. |
Remove it from Headers. |
| The signature never matches | The receiver hashes a parsed or re-serialized body, or the compressed bytes. | Hash the raw body, after decompressing it. |
Next steps
- Log streams: retries, the failing state and what happens after 24 hours.
- Redact sensitive fields: decide what a stream can carry.
- Stream logs to an OpenTelemetry collector: receive traces in place of JSON documents.
Last updated November 13, 2026