Event Labels
Labels are key-value pairs attached to events and used for filtering and routing webhooks to subscriptions. They enable powerful use cases like multi-tenancy, environment-based routing, and priority filtering.
What Are Labels?
Labels are metadata attached to events that subscriptions can filter on. Each label consists of:
- Key: A string identifier (e.g.,
tenant_id,environment,priority) - Value: A string value (e.g.,
tenant_123,production,high)
Every event must include at least one label. The API will reject events with an empty labels: {} object. This ensures proper routing and filtering of events to subscriptions.
{
"event_id": "evt_123",
"event_type": "user.account.created",
"labels": {
"tenant_id": "tenant_abc",
"environment": "production",
"region": "us-east-1",
"priority": "high"
},
"payload": { ... }
}
How Labels Work
Set Up Environment Variables
# Set your service token (from dashboard)
export HOOK0_TOKEN="YOUR_TOKEN_HERE"
export HOOK0_API="https://app.hook0.com/api/v1" # Replace by your domain (or http://localhost:8081 locally)
# Set your application ID (shown in dashboard URL or application details)
export APP_ID="YOUR_APPLICATION_ID_HERE"
Save these values:
# Save to .env file for later use
cat > .env <<EOF
HOOK0_TOKEN=$HOOK0_TOKEN
HOOK0_API=$HOOK0_API
APP_ID=$APP_ID
EOF
Event Sending with Labels
When sending an event, include labels in the request:
curl -X POST "$HOOK0_API/event" \
-H "Authorization: Bearer $HOOK0_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"application_id": "'"$APP_ID"'",
"event_id": "evt_456",
"event_type": "user.account.created",
"payload": "{\"user_id\": \"user_789\"}",
"payload_content_type": "application/json",
"labels": {
"tenant_id": "acme_corp",
"environment": "production",
"source": "web_app"
}
}'
Subscription Filtering
Subscriptions use labels to filter events:
curl -X POST "$HOOK0_API/subscriptions" \
-H "Authorization: Bearer $HOOK0_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"application_id": "'"$APP_ID"'",
"is_enabled": true,
"event_types": ["user.account.created", "user.account.updated"],
"labels": {
"tenant_id": "acme_corp"
},
"description": "Webhooks for ACME Corp tenant",
"target": {
"type": "http",
"method": "POST",
"url": "https://acme.example.com/webhooks"
}
}'
Filtering logic:
- Subscription receives event if all subscription labels match the event labels
- If event does not have a required label, subscription will not receive it
- Multiple label filters per subscription are supported
Common Use Cases
1. Multi-Tenancy
Route events to tenant-specific webhooks:
// Send event with tenant label
await hook0.sendEvent({
eventType: 'invoice.created',
payload: { invoiceId: 'inv_123', amount: 99.99 },
labels: {
tenant_id: 'acme_corp',
organization: 'org_456'
}
});
// Tenant A subscription (only receives tenant_a events)
{
"labels": { "tenant_id": "acme_corp" },
"target": { "url": "https://acme.example.com/webhooks" }
}
// Tenant B subscription (only receives tenant_b events)
{
"labels": { "tenant_id": "globex_inc" },
"target": { "url": "https://globex.example.com/webhooks" }
}
Benefits:
- Complete data isolation between tenants
- Tenants only receive their own events
- Scalable to thousands of tenants
2. Environment Filtering
Separate production, staging, and development events:
// Production event
await hook0.sendEvent({
eventType: 'deployment.completed',
labels: { environment: 'production' }
});
// Staging event
await hook0.sendEvent({
eventType: 'deployment.completed',
labels: { environment: 'staging' }
});
Subscriptions filter by environment:
{
"labels": { "environment": "production" },
"description": "Production monitoring",
"target": { "url": "https://monitoring.prod.example.com/webhooks" }
}
3. Priority Routing
Route high-priority events to dedicated endpoints:
// High-priority event
await hook0.sendEvent({
eventType: 'payment.transaction.failed',
labels: {
priority: 'critical',
alert_team: 'payments'
}
});
// Normal-priority event
await hook0.sendEvent({
eventType: 'payment.transaction.succeeded',
labels: { priority: 'normal' }
});
Subscriptions handle priorities differently:
{
"labels": { "priority": "critical" },
"description": "Critical alerts to Slack",
"target": {
"url": "https://hooks.slack.com/services/critical-alerts",
"headers": { "X-Priority": "urgent" }
}
}
4. Geographic Routing
Route events based on region or data center:
await hook0.sendEvent({
eventType: 'order.order.created',
payload: { orderId: 'ord_123' },
labels: {
region: 'us-east-1',
country: 'US'
}
});
Region-specific subscriptions:
{
"labels": { "region": "us-east-1" },
"description": "US East fulfillment center",
"target": { "url": "https://fulfillment.us-east.example.com/webhooks" }
}
5. Feature Flags
Route events based on feature enablement:
await hook0.sendEvent({
eventType: 'user.account.created',
labels: {
feature_new_onboarding: 'enabled',
experiment_group: 'variant_a'
}
});
6. Source Tracking
Identify event origin:
await hook0.sendEvent({
eventType: 'purchase.completed',
labels: {
source: 'mobile_app',
platform: 'ios',
version: '2.1.0'
}
});
Label Structure
JSON Format
Labels are a flat JSON object with string keys and string values:
{
"labels": {
"key1": "value1",
"key2": "value2",
"key3": "value3"
}
}
Validation Rules
| Constraint | Value | Error Code |
|---|---|---|
| Max labels per event | 10 | labels-size |
| Key length | 1-50 characters | labels-property-length |
| Value length | 1-50 characters | labels-property-length |
| Value type | String only | labels-property-type |
// ✅ Valid labels
{
"tenant": "acme",
"env": "prod",
"region": "us"
}
// ❌ Too many labels (max 10)
{
"label1": "value1",
"label2": "value2",
// ... 11 labels total
}
// ❌ Value too long (max 50 chars)
{
"key": "this_value_is_way_too_long_and_exceeds_the_fifty_character_limit"
}
// ❌ Non-string value
{
"priority": 1, // Must be "1" (string)
"active": true // Must be "true" (string)
}
Reserved Labels
Hook0 does not strictly reserve label keys, but common conventions exist:
Recommended Standard Labels
{
"tenant_id": "unique_tenant_identifier",
"environment": "production|staging|development",
"region": "aws_region_or_datacenter",
"priority": "critical|high|normal|low",
"source": "api|web|mobile|batch",
"version": "api_or_schema_version"
}
Label Naming Conventions
✅ Good practices:
- Use snake_case:
tenant_id,event_source - Be descriptive:
payment_providernotpp - Use consistent naming across events
- Keep keys short but meaningful
❌ Avoid:
- Camel case:
tenantId(usetenant_id) - Special characters:
tenant@id,tenant.id - Generic names:
data,info,value - Sensitive information:
credit_card,ssn,password
Advanced Patterns
Multi-Label Filtering (Multiple Subscriptions)
Since subscriptions support only one label filter, use multiple subscriptions for AND logic:
// Event with multiple labels
await hook0.sendEvent({
eventType: 'order.order.created',
labels: {
tenant_id: 'acme_corp',
environment: 'production',
priority: 'high'
}
});
Create separate subscriptions for different filtering needs:
// Subscription 1: All ACME Corp events
{
"labels": { "tenant_id": "acme_corp" },
"target": { "url": "https://acme.example.com/all-events" }
}
// Subscription 2: All production events (all tenants)
{
"labels": { "environment": "production" },
"target": { "url": "https://monitoring.example.com/production" }
}
// Subscription 3: All high-priority events
{
"labels": { "priority": "high" },
"target": { "url": "https://alerts.example.com/high-priority" }
}
Each subscription filters independently. An event with labels {tenant_id: "acme_corp", environment: "production"} will trigger both subscriptions above.
Wildcard Simulation
Hook0 does not support wildcard labels, but you can achieve similar results:
// Strategy 1: Use hierarchical values
labels: {
scope: "acme_corp.us_east.production"
}
// Strategy 2: Use multiple labels
labels: {
tenant: "acme_corp",
region: "us_east",
env: "production"
}
// Strategy 3: Subscribe to multiple specific values
// Create subscriptions for tenant_a, tenant_b, tenant_c separately
Dynamic Label Values
Generate labels programmatically:
function sendOrderEvent(order) {
return hook0.sendEvent({
eventType: 'order.order.created',
payload: order,
labels: {
tenant_id: order.tenantId,
environment: process.env.NODE_ENV,
region: process.env.AWS_REGION,
order_total: categorizeOrderTotal(order.total), // "small"|"medium"|"large"
customer_type: order.customer.isPremium ? "premium" : "standard"
}
});
}
function categorizeOrderTotal(total) {
if (total < 50) return 'small';
if (total < 500) return 'medium';
return 'large';
}
Label-Based Subscription Management
Organize subscriptions using labels:
// Create subscription with metadata describing label usage
await hook0.createSubscription({
eventTypes: ['payment.transaction.succeeded'],
labelKey: 'tenant_id',
labelValue: 'acme_corp',
description: 'ACME Corp payment webhooks',
metadata: {
tenant_name: 'ACME Corporation',
tenant_tier: 'enterprise',
contact_email: 'webhooks@acme.com',
label_strategy: 'tenant_isolation'
}
});
Best Practices
Design Principles
- Keep labels simple: Use for filtering, not business logic
- Use consistent naming: Establish conventions and stick to them
- Document label schema: Maintain documentation of label keys and allowed values
- Limit label count: Use only necessary labels (remember 10 max)
- Avoid PII: Do not include sensitive data in labels
Example Label Schema Documentation
# labels-schema.yaml
labels:
tenant_id:
type: string
pattern: "^[a-z0-9_]{3,50}$"
description: "Unique tenant identifier"
required: true
environment:
type: string
enum: [production, staging, development]
description: "Deployment environment"
required: true
priority:
type: string
enum: [critical, high, normal, low]
description: "Event priority level"
required: false
region:
type: string
pattern: "^[a-z]{2}-[a-z]+-[0-9]$"
description: "AWS region identifier"
required: false
Implementation Checklist
Event sending:
- ✅ Validate label values before sending
- ✅ Use constants for label keys
- ✅ Include required labels (tenant_id, environment)
- ✅ Keep label count under 10
- ✅ Use string values only
Subscription configuration:
- ✅ Choose appropriate labels for filtering
- ✅ Document subscription filtering logic
- ✅ Test subscription receives expected events
- ✅ Monitor subscription delivery metrics
Code Example: Label Helper
// labels.js
const REQUIRED_LABELS = ['tenant_id', 'environment'];
const MAX_LABELS = 10;
const MAX_LENGTH = 50;
class LabelValidator {
static validate(labels) {
// Check required labels
for (const key of REQUIRED_LABELS) {
if (!labels[key]) {
throw new Error(`Missing required label: ${key}`);
}
}
// Check label count
if (Object.keys(labels).length > MAX_LABELS) {
throw new Error(`Too many labels (max ${MAX_LABELS})`);
}
// Check lengths and types
for (const [key, value] of Object.entries(labels)) {
if (typeof value !== 'string') {
throw new Error(`Label ${key} must be a string`);
}
if (key.length > MAX_LENGTH || value.length > MAX_LENGTH) {
throw new Error(`Label ${key} exceeds max length ${MAX_LENGTH}`);
}
}
return true;
}
static create(tenant, environment, extra = {}) {
const labels = {
tenant_id: tenant,
environment: environment,
...extra
};
this.validate(labels);
return labels;
}
}
// Usage
const labels = LabelValidator.create('acme_corp', 'production', {
region: 'us-east-1',
priority: 'high'
});
await hook0.sendEvent({
eventType: 'order.order.created',
labels
});
Troubleshooting
Events Not Reaching Subscription
Symptom: Events sent but subscription not triggered
Causes:
- Label mismatch: Event label value does not match subscription filter
- Missing label: Event does not include the filtered label key
- Case sensitivity: Label values are case-sensitive
Solutions:
// Check event labels
const event = await hook0.getEvent('evt_123');
console.log('Event labels:', event.labels);
// Check subscription filter
const subscription = await hook0.getSubscription('sub_456');
console.log('Subscription labels:', subscription.labels);
// Verify match (all subscription labels must match event labels)
const matches = Object.entries(subscription.labels).every(
([key, value]) => event.labels[key] === value
);
console.log('Labels match:', matches);
Validation Errors
Symptom: 400 Bad Request with label validation error
Error codes:
labels-size: Too many labels (max 10)labels-property-length: Key or value exceeds 50 characterslabels-property-type: Non-string value detected
Solutions:
// Fix: Reduce label count
// ❌ Bad (11 labels)
labels: { l1: "v1", l2: "v2", ..., l11: "v11" }
// ✅ Good (10 labels)
labels: { l1: "v1", l2: "v2", ..., l10: "v10" }
// Fix: Shorten values
// ❌ Bad (too long)
labels: { key: "this_is_a_very_long_value_that_exceeds_fifty_characters_limit" }
// ✅ Good
labels: { key: "short_value" }
// Fix: Convert to string
// ❌ Bad (wrong type)
labels: { priority: 1, active: true }
// ✅ Good
labels: { priority: "1", active: "true" }
Performance Considerations
Issue: Too many subscriptions with different label filters
Impact:
- Increased database queries
- Slower event routing
- Higher resource usage
Optimization:
// ❌ Inefficient: 100 subscriptions for 100 tenants
for (const tenant of tenants) {
await createSubscription({
labelKey: 'tenant_id',
labelValue: tenant.id,
target: { url: `https://${tenant.domain}/webhooks` }
});
}
// ✅ Better: Single webhook endpoint handles routing
await createSubscription({
labelKey: 'environment',
labelValue: 'production',
target: { url: 'https://webhook-router.example.com/receive' }
});
// Webhook router extracts tenant from event.labels.tenant_id
// and forwards to appropriate tenant endpoint
API Reference
Event Creation with Labels
POST /api/v1/event
Content-Type: application/json
Authorization: Bearer TOKEN
{
"application_id": "app_123",
"event_id": "evt_456",
"event_type": "user.account.created",
"payload": "{}",
"labels": {
"tenant_id": "acme_corp",
"environment": "production"
}
}
Subscription Creation with Label Filter
POST /api/v1/subscriptions
Content-Type: application/json
Authorization: Bearer TOKEN
{
"application_id": "app_123",
"event_types": ["user.account.created"],
"labels": {
"tenant_id": "acme_corp"
},
"target": {
"type": "http",
"method": "POST",
"url": "https://example.com/webhooks"
}
}
Querying Events by Labels
GET /api/v1/events?application_id=app_123
Authorization: Bearer TOKEN
Response includes labels for filtering client-side:
{
"events": [
{
"event_id": "evt_123",
"labels": {
"tenant_id": "acme_corp",
"environment": "production"
}
}
]
}
Next Steps
- Event Schemas Reference - Learn about event structure
- API Documentation - Full API documentation
- Multi-tenant Architecture - Architecture patterns
- Event Types Tutorial - Hands-on tutorial