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_idtouserIdin 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
- Navigate to Events > Contracts
- Click Create Contract
- Enter the event name this contract applies to (e.g.,
Order Completed) - Define the property schema
- Set the violation policy
- Click Save
Contract Schema
A contract defines the expected properties for an event:
| Field | Description |
|---|---|
| Property name | The property key (e.g., order_id, total, products) |
| Type | Expected data type |
| Required | Whether the property must be present |
| Description | Human-readable description (for documentation) |
Supported Types
| Type | Description | Example Values |
|---|---|---|
string | Text value | "order_123", "Jane Smith" |
number | Integer or decimal | 42, 99.99, -1 |
boolean | True or false | true, false |
integer | Integer only (no decimals) | 1, 42, 1000 |
array | Ordered list of values | ["tag1", "tag2"], [1, 2, 3] |
object | Nested key-value structure | { "name": "Product", "price": 29.99 } |
datetime | ISO 8601 timestamp string | "2025-03-15T14:30:00Z" |
any | Accept 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:
| Policy | Behavior | Use Case |
|---|---|---|
| Allow | Accept the event as-is. Log the violation but do not modify or reject the event. | Early development — track violations without blocking events. |
| Drop Property | Accept the event but remove properties that violate the schema (wrong type, unplanned properties if disallowed). | Production — enforce types while keeping the event. |
| Reject | Reject the entire event with 422 Unprocessable Entity. | Strict environments — bad events should never enter the system. |
Violation Types
| Violation | Description | Example |
|---|---|---|
| Missing required property | A required property is not present | Order Completed missing order_id |
| Wrong type | A property has the wrong data type | total is "99.99" (string) instead of 99.99 (number) |
| Unplanned property | A property exists that is not in the contract | discount_pct is sent but not in the schema |
| Invalid nested schema | An object or array element violates its nested schema | products[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
- Open an existing contract
- Click New Version
- Modify the schema (add properties, change types, update requirements)
- Set the activation date (when this version takes effect)
- 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:
| Change | Strategy |
|---|---|
| Add a new required property | First add it as optional, update all sources to send it, then make it required in a new version |
| Remove a property | Set allow_unplanned_properties: true, then remove from contract. Old events with the property still pass. |
| Change a property type | Create a new property with the correct type, update sources, then remove the old property |
| Rename a property | Use an event transformation to rename in-flight, then update the contract |
Monitoring Contract Violations
The Events > Contracts dashboard shows:
| Metric | Description |
|---|---|
| Violation rate | Percentage of events that violate the contract |
| Violations by type | Breakdown by missing property, wrong type, unplanned property |
| Violations by source | Which write keys are producing violations |
| Violation trend | Time-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}/violationsBest Practices
- Start with allow mode — When first deploying contracts, use the
allowpolicy to observe violations without breaking existing integrations - Graduate to drop or reject — Once violation rates are near zero, switch to
drop_propertyorrejectto 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