---
title: Send events to an HTTPS endpoint
description: Configure HttpSink, authenticate it, reuse your own client, and understand what one POST per event costs.
---
# Send events to an HTTPS endpoint
`HttpSink` POSTs each event as JSON to a URL you choose. It needs the
`remote-sinks` feature.
```toml
[dependencies]
rtb-telemetry = { version = "0.7", features = ["remote-sinks"] }
```
## Configure and build it
```rust
use std::sync::Arc;
use std::time::Duration;
use rtb_telemetry::{HttpSink, HttpSinkConfig};
use secrecy::SecretString;
let config = HttpSinkConfig {
endpoint: "https://telemetry.example.com/v1/events".parse()?,
bearer_token: Some(SecretString::from(token)),
timeout: Duration::from_secs(5),
user_agent: format!("{}/{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")),
allow_insecure_endpoint: false,
};
let sink = Arc::new(HttpSink::new(config)?);
```
`HttpSinkConfig::default()` exists, but its endpoint is the placeholder
`https://telemetry.invalid/` — always override it. Every field's default is
listed in [Sinks](../reference/sinks.md#httpsinkconfig-fields).
## Authenticate
Set `bearer_token` and the sink sends
`Authorization: Bearer <token>`. Hold it as a `SecretString` — that is the
type the field takes, and it keeps the value out of `Debug` output and
zeroes it on drop.
If your endpoint needs a different scheme or a custom header, `HttpSink`
cannot do it. Wrap your own [custom sink](write-a-custom-sink.md) around
`reqwest` instead.
## Reuse a client you already have
```rust
let sink = HttpSink::with_client(config, my_client);
```
The injected client's own timeout and user agent win; the `timeout` and
`user_agent` fields in the config are ignored. `with_client` cannot fail,
so an endpoint-scheme problem surfaces from the first `emit` rather than
from the constructor.
## What happens with a plain-http endpoint
It is refused:
```text
HTTP telemetry sink error: endpoint scheme "http" not permitted (set allow_insecure_endpoint for tests)
```
`allow_insecure_endpoint: true` lifts that, and exists for pointing tests
at a local mock server. Shipping it enabled sends usage data, and any
bearer token, in clear text.
## Know what one POST per event costs
`emit` sends one request and awaits it. There is no batching, no queue and
no retry: a slow endpoint slows the command that recorded the event, up to
`timeout`, and a failed POST loses that event.
If the endpoint is remote and the tool is interactive, consider recording
to a [file](write-events-to-a-file.md) and shipping it separately, or
buffering in a [custom sink](write-a-custom-sink.md).
## Handle the failures
Every failure is `TelemetryError::Http`: a refused scheme, a transport
error (`error sending request for url (…)`), or a non-2xx response
(`non-2xx response: 404 Not Found`). Log and continue — a telemetry POST
failing is not a reason to fail the user's command.
## What the endpoint receives
The redacted event, plus a top-level `severity` of `"ERROR"` when
`err_msg` is set and `"INFO"` otherwise:
```json
{"name":"cmd.run","tool":"mytool","tool_version":"1.0.0","machine_id":"…","timestamp_utc":"…","attrs":{},"severity":"INFO"}
```