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:

  1. Create the event type automatically
  2. Set it to active status
  3. 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:

Node.js
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);
Python
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"]}')
Go
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
<?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 CreationUse Explicit Creation
Rapid prototypingProduction environments
Local developmentWhen you want payload validation
Quick testingTeam collaboration (documented event catalog)
API-first workflows (define types before sending)
Best Practice: Create event types explicitly in production to enable payload validation and provide documentation for your team and API consumers.

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.canceled
Format: Always use lowercase with dot notation. This format is standard across Stripe, GitHub, Shopify, and other major platforms.

Use Past Tense

Events represent things that already happened, so use past tense:

Good (Past Tense)Bad (Present/Future)
user.createduser.create
payment.succeededpayment.success
order.shippedorder.ship
subscription.canceledsubscription.cancel

Be Descriptive

Event type names should clearly communicate what happened:

Good (Descriptive)Bad (Vague)
payment.succeededpayment.ok
user.password_reset_requesteduser.action
subscription.trial_endingsubscription.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.reactivated

Payments & Billing

payment.succeeded
payment.failed
payment.refunded
invoice.created
invoice.paid
invoice.overdue
invoice.voided
subscription.created
subscription.renewed
subscription.canceled
subscription.trial_ending

Orders & Fulfillment

order.created
order.confirmed
order.processing
order.shipped
order.delivered
order.returned
order.canceled

Event 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 delivered
Empty array = all events: If event_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.v2

Pros: 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

Recommended approach: Use additive-only changes (Option 3) for minor updates. Only version event types (Option 1) for breaking changes.

Schema Definition

While Hook Mesh doesn't enforce schemas (future feature), documenting your event payloads helps customers integrate correctly:

Event Schema Example
// 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"
  }
}
Consistent structure: Keep event_type, event_id, and timestamp at the root level. Put domain-specific data in a data object.

Breaking vs. Non-Breaking Changes

Change TypeBreaking?Action Required
Add new optional fieldNoSafe to deploy
Add new required fieldYesVersion event type
Remove existing fieldYesVersion event type
Change field typeYesVersion event type
Rename fieldYesVersion event type
Add new event typeNoSafe 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_stock

SaaS 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.revoked

Next Steps