neondatabase/neon · warning

Failed to capture events: {}, {}

Error message

Failed to capture events: {}, {}

What it means

capture_event POSTs a single event {api_key, distinct_id, event, properties} to {public_api_url}/capture/ using the client_api_key. Any non-2xx response produces this error with the HTTP status and PostHog's response body. The body typically names the concrete problem: invalid API key, invalid JSON/property types, or payload-size limits.

Source

Thrown at libs/posthog_client_lite/src/lib.rs:627

    ) -> anyhow::Result<()> {
        // PUBLIC_URL/capture/
        let url = format!("{}/capture/", self.config.public_api_url);
        let response = self
            .client
            .post(url)
            .body(serde_json::to_string(&json!({
                "api_key": self.config.client_api_key,
                "distinct_id": distinct_id,
                "event": event,
                "properties": properties,
            }))?)
            .send()
            .await?;
        let status = response.status();
        let body = response.text().await?;
        if !status.is_success() {
            return Err(anyhow::anyhow!(
                "Failed to capture events: {}, {}",
                status,
                body
            ));
        }
        Ok(())
    }

    pub async fn capture_event_batch(&self, events: &[CaptureEvent]) -> anyhow::Result<()> {
        // PUBLIC_URL/batch/
        let url = format!("{}/batch/", self.config.public_api_url);
        let response = self
            .client
            .post(url)
            .body(serde_json::to_string(&json!({
                "api_key": self.config.client_api_key,
                "batch": events,
            }))?)
            .send()

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Check the embedded body text: fix the client_api_key for 401, the properties shape for 400
  2. Ensure public_api_url points at the capture endpoint of the correct PostHog region/project
  3. Shrink or split very large property payloads (move big blobs out of event properties)
  4. Treat 5xx as transient: retry with backoff or drop the event (it is telemetry), but log the body
Defensive patterns

Strategy: retry

Validate before calling

// Only send events you know are well-formed; cheap client-side checks avoid 400s:
fn event_is_well_formed(event: &str, distinct_id: &str, props: &serde_json::Value) -> bool {
    !event.is_empty()
        && !distinct_id.is_empty()
        && props.is_object() // posthog expects an object of properties
        && serde_json::to_string(props).map(|s| s.len() < 512 * 1024).unwrap_or(false)
}

Try / catch

// Telemetry must never take down the caller: retry transient, swallow-and-log the rest.
let mut bo = ExponentialBackoff::default();
loop {
    match client.capture_event(event, distinct_id, &props).await {
        Ok(()) => break,
        Err(e) => {
            let msg = format!("{e:#}");
            let transient = msg.contains(" 5") || msg.contains("timeout") || msg.contains(" 429, ");
            if !transient || bo.next_backoff().is_none() {
                tracing::warn!("posthog capture dropped: {msg}");
                break; // analytics is best-effort
            }
        }
    }
}

Prevention

When it happens

Trigger: 401 'invalid API key' when client_api_key is wrong or from a different project; 400 when properties contain unserializable/wrong-typed values or the event name is empty; 413 when a huge properties payload exceeds the capture limit; 5xx during PostHog incidents.

Common situations: Using the server/personal key where the project API key is required (or vice versa); events sent to the wrong region (EU vs US public_api_url); oversized batch-like properties on a single event; proxy/WAF rejecting the JSON POST.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/b095364d856e649a. Report an issue: GitHub.