Handle ingestion failures

When sending usage events to Zenskar via API, it's important to understand how ingestion failures are handled, and how you can prevent data loss due to validation errors or network issues.

Why this matters

If a usage event does not show up in Zenskar, the assumption is often that it was lost by our system. In reality, ingestion can fail in two main ways:

1. Rejected by Zenskar due to validation errors

These are non-retriable failures. Zenskar received the event but rejected it due to issues like:

  • Malformed JSON
  • Invalid metric name
  • Incorrect timestamp

In such cases, Zenskar responds with an HTTP 4xx status code and a descriptive error message. If your system doesn't handle this response correctly, the event is lost.

2. Event never reached Zenskar

These are retriable failures. Events might not reach us due to:

  • Network outages on your side
  • Intermediate routing issues
  • Zenskar service downtime

Since the event never reaches us, we can’t log it or notify you. If you don’t retry or store it, the event is lost permanently.


Recommended approach: implement a dead letter queue (DLQ)

🔖

Definition

A dead letter queue (DLQ) is a mechanism to capture failed events so that they can be retried or examined later.

A DLQ is a holding area for failed events, either due to validation errors or delivery issues. This ensures no event is lost, even if initial ingestion fails.

sequenceDiagram
    participant Your system
    participant Zenskar API
    participant DLQ

    Your system->>Zenskar API: Send usage event
    alt Success
        Zenskar API-->>Your system: 200 OK
    else Validation error
        Zenskar API-->>Your system: 4xx error
        Your system->>DLQ: Store failed event
    else Network error
        Note right of Your system: No response received
        Your system->>DLQ: Store failed event
    end

Ways to implement a DLQ

You can implement a DLQ in many ways, depending on your system architecture and event volume. Here are some common approaches:

MethodDescription
File-based loggingWrite failed events to a local file. Easy to set up but less scalable.
Database fallbackStore failed events in a database table for review and manual replay.
Message queues (e.g. Kafka, RabbitMQ)Send failed events to a dedicated DLQ topic or queue. Suitable for large-scale systems.
Cloud-native DLQ (e.g. AWS SQS DLQ)Use managed DLQ support with retry and failure handling built in.

Error responses from Zenskar

Below is a sample of a valid event upload payload. Use this as a reference to ensure your requests are correctly structured:

[
  {
    "data": {
      "campaign_id": "sample campaign_id 8",
      "impressions": 74
    },
    "timestamp": "2025-06-28 23:44:47",
    "customer_id": "c03"
  }
]

When uploading usage events via the ingestion API, your request may be rejected with a 4xx HTTP status code if there are issues with the payload. This guide outlines all possible 4xx errors, explains their causes, and provides real examples to help you debug and resolve issues effectively.

HTTP 4xx error codes

404 Not Found

When:

  • The request body is not valid JSON and cannot be parsed.

Example Response:

{
  "error": "invalid character '}' looking for beginning of object key string"
}
413 Payload Too Large

When:

  • The request body exceeds 1MB in size.

Example Response:

{
  "error": "Payload too large"
}
422 Unprocessable Entity (validation errors)

When:

  • The request body is valid JSON, but fails schema validation (missing keys, wrong types, unexpected keys, etc.).

Example Response:

{
  "error": "Invalid type for key: impressions. Expected Int64, got string"
}

Validation error messages

The API validates your event payload against a schema. If validation fails, you will receive a 422 response with a descriptive error message. Below are the types of validation errors you may encounter:

Missing or unexpected keys
  • Missing key: The payload is missing a required field.
{
  "error": "Missing key: impressions"
}
  • Unexpected key: The payload contains a field not defined in the schema.
  {
    "error": "Unexpected key in payload: extra_field"
  }
Type mismatches
  • String expected, got number:
{
  "error": "Invalid type for key: campaign_id. Expected String, got float64"
}
  • Int64 expected, got string:
  {
    "error": "Invalid type for key: impressions. Expected Int64, got string"
  }
  • Float64 expected, got string:
  {
    "error": "Invalid type for key: value. Expected Float64, got string"
  }
  • Boolean expected, got string:
  {
    "error": "Invalid type for key: is_active. Expected Bool, got string"
  }
Date/time format errors
  • Date32 expected, got wrong format:
{
  "error": "Invalid type for key: start_date. Expected Date32, got string"
}
  • DateTime64 expected, got wrong format:
  {
    "error": "Invalid type for key: timestamp. Expected Date32/DateTime64, got string"
  }
UUID format errors
  • UUID expected, got invalid string:
  {
    "error": "Invalid type for key: user_id. Expected UUID, got string"
  }
Nested object errors
  • Expected object, got something else:
{
  "error": "Invalid type for key: data. Expected Object, got string"
}
  • Nested validation error:
  {
    "error": "Invalid type for key: nested_field. Expected Int64, got string"
  }

Example error responses

Here are some real-world examples of error responses you might see:

Example 1: Type mismatch

Request:

{
  "data": {
    "campaign_id": "sample_campaign_id_8",
    "impressions": "74"
  },
  "timestamp": "2025-06-28 23:44:47",
  "customer_id": "c03"
}

Response:

{
  "error": "Invalid type for key: impressions. Expected Int64, got string"
}
Example 2: Missing key

Request:

{
  "data": {
    "campaign_id": "sample_campaign_id_8"
  },
  "timestamp": "2025-06-28 23:44:47",
  "customer_id": "c03"
}

Response:

{
  "error": "Missing key: impressions"
}
Example 3: Unexpected key

Request:

{
  "data": {
    "campaign_id": "sample_campaign_id_8",
    "impressions": 74,
    "extra_field": "not_allowed"
  },
  "timestamp": "2025-06-28 23:44:47",
  "customer_id": "c03"
}

Response:

{
  "error": "Unexpected key in payload: extra_field"
}

Best Practices

  • Check the schema: Make sure your payload matches the expected schema for the usage event.
  • Use correct types: Numbers should be numbers, not strings; dates should be in the correct format.
  • Avoid extra fields: Only include fields defined in the schema.
  • Keep payloads small: Stay under 1MB per request.

Summary Table

HTTP StatusWhen it OccursExample Error Message
404Invalid JSONinvalid character ...
413Payload too large (>1MB)Payload too large
422Validation error (schema/type/format)Invalid type for key: ..., Missing key: ..., etc.