EventsEvent Contracts

Event Contracts

Event contracts enforce data quality by defining schemas for your events. A contract specifies which properties an event should have, their data types, and what happens when an event violates the schema.

Why Contracts?

Without contracts, event data drifts over time:

  • A developer renames user_id to userId in one service but not another
  • A new property is added with inconsistent types (string in web, number in mobile)
  • Required properties are accidentally omitted in a deploy
  • Unused properties accumulate and bloat your warehouse tables

Contracts catch these issues at ingestion time, before bad data propagates to your warehouse, audiences, and downstream destinations.

Creating a Contract

Via the UI

  1. Navigate to Events > Contracts
  2. Click Create Contract
  3. Enter the event name this contract applies to (e.g., Order Completed)
  4. Define the property schema
  5. Set the violation policy
  6. Click Save

Contract Schema

A contract defines the expected properties for an event:

FieldDescription
Property nameThe property key (e.g., order_id, total, products)
TypeExpected data type
RequiredWhether the property must be present
DescriptionHuman-readable description (for documentation)

Supported Types

TypeDescriptionExample Values
stringText value"order_123", "Jane Smith"
numberInteger or decimal42, 99.99, -1
booleanTrue or falsetrue, false
integerInteger only (no decimals)1, 42, 1000
arrayOrdered list of values["tag1", "tag2"], [1, 2, 3]
objectNested key-value structure{ "name": "Product", "price": 29.99 }
datetimeISO 8601 timestamp string"2025-03-15T14:30:00Z"
anyAccept any type(no type enforcement)

For array types, you can optionally specify the element type (e.g., “array of objects”). For object types, you can define nested property schemas.

Example Contract

Here is a contract for the Order Completed event:

{
  "event": "Order Completed",
  "properties": [
    {
      "name": "order_id",
      "type": "string",
      "required": true,
      "description": "Unique order identifier"
    },
    {
      "name": "total",
      "type": "number",
      "required": true,
      "description": "Order total amount in dollars"
    },
    {
      "name": "currency",
      "type": "string",
      "required": true,
      "description": "ISO 4217 currency code (e.g., USD, EUR)"
    },
    {
      "name": "products",
      "type": "array",
      "required": true,
      "description": "List of products in the order",
      "items": {
        "type": "object",
        "properties": [
          { "name": "product_id", "type": "string", "required": true },
          { "name": "name", "type": "string", "required": true },
          { "name": "price", "type": "number", "required": true },
          { "name": "quantity", "type": "integer", "required": true }
        ]
      }
    },
    {
      "name": "coupon",
      "type": "string",
      "required": false,
      "description": "Coupon code applied to the order"
    },
    {
      "name": "payment_method",
      "type": "string",
      "required": false,
      "description": "Payment method used (credit_card, paypal, etc.)"
    }
  ],
  "violation_policy": "drop_property",
  "allow_unplanned_properties": true
}

Violation Policies

When an event violates the contract, SignalSmith applies the configured violation policy:

PolicyBehaviorUse Case
AllowAccept the event as-is. Log the violation but do not modify or reject the event.Early development — track violations without blocking events.
Drop PropertyAccept the event but remove properties that violate the schema (wrong type, unplanned properties if disallowed).Production — enforce types while keeping the event.
RejectReject the entire event with 422 Unprocessable Entity.Strict environments — bad events should never enter the system.

Violation Types

ViolationDescriptionExample
Missing required propertyA required property is not presentOrder Completed missing order_id
Wrong typeA property has the wrong data typetotal is "99.99" (string) instead of 99.99 (number)
Unplanned propertyA property exists that is not in the contractdiscount_pct is sent but not in the schema
Invalid nested schemaAn object or array element violates its nested schemaproducts[0].price is "free" instead of a number

Unplanned Properties

The allow_unplanned_properties setting controls whether properties not defined in the contract are accepted:

  • true (default) — Unknown properties are passed through. This is flexible but may lead to schema drift.
  • false — Unknown properties are handled according to the violation policy (dropped or cause rejection).

Contract Versioning

Contracts support versioning to manage schema evolution:

Creating a New Version

  1. Open an existing contract
  2. Click New Version
  3. Modify the schema (add properties, change types, update requirements)
  4. Set the activation date (when this version takes effect)
  5. Click Save

Version Behavior

  • Only one version is active at a time per event
  • When a new version activates, it immediately applies to all incoming events
  • Previous versions are retained for audit purposes
  • You can roll back to a previous version at any time

Migration Strategies

When evolving schemas, consider these patterns:

ChangeStrategy
Add a new required propertyFirst add it as optional, update all sources to send it, then make it required in a new version
Remove a propertySet allow_unplanned_properties: true, then remove from contract. Old events with the property still pass.
Change a property typeCreate a new property with the correct type, update sources, then remove the old property
Rename a propertyUse an event transformation to rename in-flight, then update the contract

Monitoring Contract Violations

The Events > Contracts dashboard shows:

MetricDescription
Violation ratePercentage of events that violate the contract
Violations by typeBreakdown by missing property, wrong type, unplanned property
Violations by sourceWhich write keys are producing violations
Violation trendTime-series chart of violations over the last 7/30 days

High violation rates typically indicate a source that has deployed a breaking change. Use the violation details to identify which source needs updating.

API Reference

# List all contracts
GET /api/v1/events/contracts
 
# Get a contract
GET /api/v1/events/contracts/{id}
 
# Create a contract
POST /api/v1/events/contracts
 
# Update a contract (creates a new version)
PUT /api/v1/events/contracts/{id}
 
# Delete a contract
DELETE /api/v1/events/contracts/{id}
 
# List contract violations
GET /api/v1/events/contracts/{id}/violations

Best Practices

  • Start with allow mode — When first deploying contracts, use the allow policy to observe violations without breaking existing integrations
  • Graduate to drop or reject — Once violation rates are near zero, switch to drop_property or reject to enforce the schema
  • Contract every tracked event — Even if you start with minimal properties, having a contract ensures you know what to expect
  • Review violations weekly — Check the violations dashboard to catch data quality issues early
  • Version carefully — Use the migration strategies above to avoid breaking changes