Event Types
Event types categorize your webhooks so customers can subscribe only to the events they care about. Good event type design makes your webhook system intuitive and easy to use.
What are Event Types?
Event types are labels that identify what happened in your system. When creating a webhook job, you specify an event type, and Hook Mesh delivers it only to endpoints that subscribed to that type.
// Create a webhook job with an event type
await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
application_id: 'app_xyz',
event_type: 'user.created', // Event type
payload: {
user_id: 'usr_123',
email: 'alice@example.com'
}
})
});Creating Event Types
Event types in Hook Mesh can be created in two ways: automatically on first use or explicitly via the API.
Automatic Creation
The simplest approach is to let Hook Mesh create event types automatically when you first send a webhook job:
// Node.js - Event type is created automatically
const response = await fetch('https://api.hookmesh.com/v1/webhook-jobs', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
application_id: 'app_xyz789',
event_type: 'user.created', // Created automatically if doesn't exist
payload: {
user_id: 'usr_123',
email: 'user@example.com'
}
})
});When you reference an event type that doesn't exist, Hook Mesh will:
- Create the event type automatically
- Set it to active status
- Allow it to be used immediately
This is great for rapid development and testing.
Explicit Creation via API
For production use, it's better to explicitly create event types with descriptions and optional JSON schemas:
const response = await fetch('https://api.hookmesh.com/v1/event-types', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'user.created',
description: 'Triggered when a new user account is created',
schema: {
type: 'object',
required: ['user_id', 'email'],
properties: {
user_id: { type: 'string' },
email: { type: 'string', format: 'email' },
created_at: { type: 'string', format: 'date-time' }
}
}
})
});
const eventType = await response.json();
console.log('Created:', eventType.id);import requests
import os
response = requests.post(
'https://api.hookmesh.com/v1/event-types',
headers={
'Authorization': f'Bearer {os.getenv("HOOKMESH_API_KEY")}',
'Content-Type': 'application/json'
},
json={
'name': 'user.created',
'description': 'Triggered when a new user account is created',
'schema': {
'type': 'object',
'required': ['user_id', 'email'],
'properties': {
'user_id': {'type': 'string'},
'email': {'type': 'string', 'format': 'email'},
'created_at': {'type': 'string', 'format': 'date-time'}
}
}
}
)
event_type = response.json()
print(f'Created: {event_type["id"]}')package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload := map[string]interface{}{
"name": "user.created",
"description": "Triggered when a new user account is created",
"schema": map[string]interface{}{
"type": "object",
"required": []string{"user_id", "email"},
"properties": map[string]interface{}{
"user_id": map[string]string{"type": "string"},
"email": map[string]string{"type": "string", "format": "email"},
"created_at": map[string]string{"type": "string", "format": "date-time"},
},
},
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://api.hookmesh.com/v1/event-types", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("HOOKMESH_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var eventType map[string]interface{}
json.NewDecoder(resp.Body).Decode(&eventType)
fmt.Printf("Created: %s\n", eventType["id"])
}<?php
$ch = curl_init('https://api.hookmesh.com/v1/event-types');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('HOOKMESH_API_KEY'),
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'name' => 'user.created',
'description' => 'Triggered when a new user account is created',
'schema' => [
'type' => 'object',
'required' => ['user_id', 'email'],
'properties' => [
'user_id' => ['type' => 'string'],
'email' => ['type' => 'string', 'format' => 'email'],
'created_at' => ['type' => 'string', 'format' => 'date-time']
]
]
])
]);
$response = curl_exec($ch);
$eventType = json_decode($response, true);
echo "Created: " . $eventType['id'] . "\n";
curl_close($ch);Benefits of Explicit Creation
- Documentation: Event types appear in your dashboard with descriptions
- Validation: Optional JSON schema validates payloads before sending
- Governance: Control which event types can be used
- Discoverability: Developers can browse available event types
When to Use Each Approach
| Use Automatic Creation | Use Explicit Creation |
|---|---|
| Rapid prototyping | Production environments |
| Local development | When you want payload validation |
| Quick testing | Team collaboration (documented event catalog) |
| API-first workflows (define types before sending) |
Naming Conventions
Follow these conventions for clear, maintainable event type names:
Resource.Action Format
Use the pattern resource.action where the resource is a noun and the action describes what happened:
user.created
user.updated
user.deleted
invoice.paid
invoice.failed
order.shipped
order.delivered
subscription.renewed
subscription.canceledUse Past Tense
Events represent things that already happened, so use past tense:
| Good (Past Tense) | Bad (Present/Future) |
|---|---|
| user.created | user.create |
| payment.succeeded | payment.success |
| order.shipped | order.ship |
| subscription.canceled | subscription.cancel |
Be Descriptive
Event type names should clearly communicate what happened:
| Good (Descriptive) | Bad (Vague) |
|---|---|
| payment.succeeded | payment.ok |
| user.password_reset_requested | user.action |
| subscription.trial_ending | subscription.event |
Common Event Types by Domain
User Management
user.created
user.updated
user.deleted
user.logged_in
user.logged_out
user.password_changed
user.password_reset_requested
user.email_verified
user.suspended
user.reactivatedPayments & Billing
payment.succeeded
payment.failed
payment.refunded
invoice.created
invoice.paid
invoice.overdue
invoice.voided
subscription.created
subscription.renewed
subscription.canceled
subscription.trial_endingOrders & Fulfillment
order.created
order.confirmed
order.processing
order.shipped
order.delivered
order.returned
order.canceledEvent Filtering
Your customers subscribe to specific event types when creating endpoints:
// Customer subscribes to specific events
await fetch('https://api.hookmesh.com/v1/endpoints', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
application_id: 'app_xyz',
url: 'https://api.customer.com/webhooks',
event_types: [
'user.created',
'user.updated',
'user.deleted'
]
})
});
// This endpoint will ONLY receive webhooks for these 3 event types
// user.logged_in, payment.succeeded, etc. will NOT be deliveredevent_types is empty or not specified, the endpoint receives all event types.Versioning Strategies
As your product evolves, you'll need to change event payloads. Here are three strategies:
Option 1: Version in Event Type Name
user.created.v1
user.created.v2
payment.succeeded.v1
payment.succeeded.v2Pros: Explicit versioning, customers opt-in to new versions
Cons: Requires maintaining multiple event type definitions
Option 2: Version in Payload
{
"version": "2.0",
"event_type": "user.created",
"user_id": "usr_123",
"email": "alice@example.com",
"new_field_in_v2": "value"
}Pros: Single event type, customers handle versioning in code
Cons: Customers must check version field
Option 3: Additive Changes Only (Recommended)
// Version 1 payload
{
"user_id": "usr_123",
"email": "alice@example.com"
}
// Version 2 payload (ADDS fields, doesn't remove or change existing)
{
"user_id": "usr_123",
"email": "alice@example.com",
"phone": "+1234567890", // New field
"created_at": "2026-01-20" // New field
}Pros: Backward compatible, no versioning complexity
Cons: Can't remove or change existing fields
Schema Definition
While Hook Mesh doesn't enforce schemas (future feature), documenting your event payloads helps customers integrate correctly:
// user.created event schema
interface UserCreatedEvent {
event_type: 'user.created';
event_id: string; // Unique event identifier
timestamp: string; // ISO 8601 timestamp
data: {
user_id: string; // Required
email: string; // Required
name?: string; // Optional
created_at: string; // ISO 8601
metadata?: Record<string, any>; // Optional custom data
};
}Recommended Payload Structure
{
"event_type": "user.created",
"event_id": "evt_unique_123",
"timestamp": "2026-01-20T15:30:00Z",
"data": {
"user_id": "usr_123",
"email": "alice@example.com",
"name": "Alice Johnson",
"created_at": "2026-01-20T15:30:00Z"
}
}event_type, event_id, and timestamp at the root level. Put domain-specific data in a data object.Breaking vs. Non-Breaking Changes
| Change Type | Breaking? | Action Required |
|---|---|---|
| Add new optional field | No | Safe to deploy |
| Add new required field | Yes | Version event type |
| Remove existing field | Yes | Version event type |
| Change field type | Yes | Version event type |
| Rename field | Yes | Version event type |
| Add new event type | No | Safe to deploy |
Best Practices
- Use resource.action format: Makes events scannable and organized
- Keep names lowercase: Avoids casing confusion
- Use past tense: Events are things that already happened
- Be specific: "user.password_reset_requested" better than "user.updated"
- Group related events: user.*, payment.*, order.*
- Prefer additive changes: Add fields, don't remove or change
- Document your schema: Help customers integrate correctly
- Include timestamps: Always provide event creation time
- Use consistent IDs: Resource IDs should follow same format
- Start simple: Add event types as needed, don't over-engineer
Real-World Examples
E-commerce Platform
# Customer events
customer.created
customer.updated
customer.deleted
# Order events
order.created
order.paid
order.processing
order.shipped
order.delivered
order.refunded
order.canceled
# Product events
product.created
product.updated
product.out_of_stock
product.back_in_stockSaaS Platform
# Account events
account.created
account.upgraded
account.downgraded
account.suspended
# User events
user.invited
user.joined
user.role_changed
user.removed
# Usage events
workspace.created
project.created
api_key.created
api_key.revokedNext Steps
- Create webhook jobs with your event types
- Manage event types via API (create, update, delete)
- Configure endpoint subscriptions to filter events
- Learn about payload structure and best practices