> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polymorfa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Log forwarding

> Forward sampled operational records to your HTTPS endpoint.

For the shared destination list and source selection, see [Drains](/console/drains).

Select a project and open **Project Settings → Drains**. Team owners and admins view and manage log and analytics destinations here. Members see drain metadata with destinations hidden. **Add drain → Logs** forwards operational records; **Add drain → Analytics** sends [hourly number analytics](/console/analytics-collectors#send-hourly-json-snapshots).

In **Logs**, the export menu also opens log drains and trace sampling. A drain forwards new API-request, session-state, and webhook-delivery
records after you enable it. It does not backfill earlier logs or include Settings
audit actions, message bodies, pairing data, request headers, or credentials.

## Add a destination

1. Open **Project Settings → Drains**, choose **Add drain → Logs**.
2. Use **Custom endpoint**, enter a name, and enter a public HTTPS destination URL.
3. Choose **JSON** or **NDJSON** encoding. A signing secret is generated for
   the new drain; copy it to your receiver. You can replace it with a secret of
   16–256 characters, regenerate it, or clear it for unsigned delivery.
4. Optionally enable **Custom headers** and add names and values.
5. Choose **Add drain** to save.

A project supports up to five drains across logs and analytics. The list shows whether each drain is
enabled or paused, pending and failed delivery counts, and its latest recorded
HTTP status or error. Pause, resume, or delete a drain from that list. Deleting
a drain removes its queued deliveries. Saved custom header values and signing
secrets are not returned by the configuration view. Team members see drain metadata; destination URLs and header names are visible only to owners and admins. Source, number scope, window and encoding are fixed when a drain is created; create a new drain to change them.

**Native integrations** is marked **Coming soon** and is unavailable.
Destinations must resolve to public addresses. Redirects, URL credentials,
fragments, and reserved transport or signature headers are rejected.

## Sample forwarded records

Open **Trace sampling** to add up to 20 rules. Each rule sets a percentage from
0 to 100 and optionally matches a log type, event kind, or supported identifier
and value. Rules run in their displayed order; the first matching rule applies.
Records without a matching rule are forwarded at 100%.

Sampling groups records by request ID or event ID when one exists. Repeated
records with the same identity receive the same deterministic sampling decision
for a given rate. A webhook's delivery updates share its event identity. This
control samples operational records sent to drains; it does not change stored
logs, Settings audit history, or message retention.

## Receive deliveries

With **JSON**, your endpoint receives an HTTPS **POST** with `Content-Type: application/json`.
The envelope contains a stable delivery ID and one operational record:

```json theme={null}
{
  "version": 1,
  "deliveryId": "018f0000-0000-7000-8000-000000000001",
  "records": [
    {
      "id": "api:018f0000-0000-7000-8000-000000000002",
      "projectId": "018f0000-0000-7000-8000-000000000003",
      "type": "api",
      "kind": "messages.send",
      "metadata": {
        "httpMethod": "POST",
        "route": "/api/messages",
        "httpStatus": 200,
        "requestId": "5f0c2a8e-3b1d-4c6f-9e2a-7d4b1c8f6a30",
        "traceId": "4bf92f3577b34da6a3ce929d0e0e4736"
      }
    }
  ]
}
```

With **NDJSON**, the content type is `application/x-ndjson`. Each line contains
one envelope with `version`, `deliveryId` and a single `record` object. A delivery
contains one line and ends with a newline; line breaks inside strings remain
escaped. Logs and analytics both support this encoding.

```json theme={null}
{
  "version": 1,
  "deliveryId": "018f0000-0000-7000-8000-000000000001",
  "record": {
    "id": "api:018f0000-0000-7000-8000-000000000002",
    "type": "api",
    "kind": "messages.send"
  }
}
```

API records include `requestId` and `traceId`. `traceId` is the request's W3C trace ID; when the request sent a
valid `traceparent` header, it is the caller's trace ID. Use it to join forwarded logs to your own traces.

Use the `Idempotency-Key` header, which matches `deliveryId`, to deduplicate
retries. A 2xx response completes a delivery. Other responses and connection
failures retry after one minute, five minutes, 15 minutes, one hour, and six
hours, for at most six attempts and no later than 24 hours after the record
was queued. Delivery is at least once: acknowledge only after your endpoint
has durably accepted the record. Retained delivery diagnostics expire after
seven days.

### Verify signatures

Every request includes `X-Polymorfa-Timestamp` in Unix seconds. With a signing
secret configured, `X-Polymorfa-Signature` contains `sha256=` followed by an HMAC-SHA256
hex digest. For NDJSON, include the trailing newline. Compute the HMAC using your signing secret over these exact bytes:

```text theme={null}
X-Polymorfa-Timestamp + "." + original HTTP request body
```

Compare signatures in constant time and reject stale timestamps. Verify the
original request bytes before parsing or reserializing JSON. Retries preserve
the delivery ID and create a fresh timestamp and signature.

Pausing stops new deliveries and new queue entries for that drain; an in-flight
request can finish. Resuming permits eligible queued deliveries to continue.
If support disables forwarding for the project, sampling and drain settings
remain visible, while new queue entries and delivery claims stop. Stored logs
and audit history remain available.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.