Webhook Integration Guide

This guide walks you through setting up webhook subscriptions with Hookbase to send events to your customers' endpoints.

Overview

Hookbase's outbound webhook system allows you to:

  • Define event types that your application produces
  • Create applications to represent your customers or tenants
  • Register webhook endpoints for each application
  • Subscribe endpoints to specific event types
  • Send events that are automatically delivered to subscribed endpoints

Getting Started

Step 1: Create Event Types

Event types define the different kinds of events your application can produce. They follow a dot-notation naming convention.

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/event-types" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order.created",
    "displayName": "Order Created",
    "description": "Triggered when a new order is placed",
    "category": "orders",
    "schema": "{\"type\": \"object\", \"properties\": {\"orderId\": {\"type\": \"string\"}, \"amount\": {\"type\": \"number\"}}}",
    "examplePayload": "{\"orderId\": \"ord_123\", \"amount\": 99.99, \"currency\": \"USD\"}"
  }'

Naming conventions:

  • Use lowercase letters and dots only
  • Start with a noun (the entity)
  • End with a past-tense verb (the action)
  • Examples: order.created, payment.succeeded, user.subscription.upgraded

Step 2: Create an Application

Applications represent your customers or tenants. Each application can have multiple endpoints.

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-applications" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "externalId": "customer_123",
    "metadata": {
      "plan": "enterprise",
      "region": "us-east-1"
    }
  }'

The externalId field allows you to reference applications by your own internal customer ID.

Step 3: Create Webhook Endpoints

Endpoints are the URLs where webhooks will be delivered. Each endpoint gets a unique signing secret.

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-endpoints" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "applicationId": "wh_app_xxx",
    "url": "https://api.customer.com/webhooks",
    "description": "Production webhook endpoint",
    "timeoutSeconds": 30
  }'

Response:

{
  "data": {
    "id": "wh_ep_xxx",
    "url": "https://api.customer.com/webhooks",
    "secret": "whsec_abc123def456...",
    "circuitState": "closed"
  },
  "warning": "Save the signing secret now. It will not be shown again."
}

Important: Store the signing secret securely. It is only returned once at creation time.

Step 4: Create Subscriptions

Subscriptions link endpoints to event types, determining which events are delivered to each endpoint.

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-subscriptions" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "endpointId": "wh_ep_xxx",
    "eventTypeId": "evt_type_xxx"
  }'

You can also subscribe to multiple event types at once:

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-subscriptions/bulk" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "endpointId": "wh_ep_xxx",
    "eventTypeIds": ["evt_type_order_created", "evt_type_order_updated", "evt_type_order_cancelled"]
  }'

Step 5: Send Events

When something happens in your application, send an event to Hookbase:

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/send-event" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "order.created",
    "payload": {
      "orderId": "ord_789",
      "customerId": "cus_456",
      "amount": 149.99,
      "currency": "USD",
      "items": [
        {"sku": "WIDGET-001", "quantity": 2, "price": 74.99}
      ]
    },
    "idempotencyKey": "order-created-ord_789"
  }'

Response:

{
  "data": {
    "eventId": "evt_xxx",
    "messagesQueued": 3,
    "endpoints": [
      {"id": "wh_ep_aaa", "url": "https://api.customer1.com/webhooks"},
      {"id": "wh_ep_bbb", "url": "https://api.customer2.com/webhooks"},
      {"id": "wh_ep_ccc", "url": "https://api.customer3.com/webhooks"}
    ]
  }
}

Event Types

Creating Event Types

Event types should be created before sending events. They provide:

  • Schema validation: Define a JSON Schema to validate payloads
  • Documentation: Help consumers understand event structure
  • Categorization: Group related events for easier management
curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/event-types" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "invoice.payment.succeeded",
    "displayName": "Invoice Payment Succeeded",
    "description": "Fired when an invoice payment is successfully processed",
    "category": "billing",
    "schema": "{\"type\": \"object\", \"required\": [\"invoiceId\", \"amount\"], \"properties\": {\"invoiceId\": {\"type\": \"string\"}, \"amount\": {\"type\": \"number\"}, \"paidAt\": {\"type\": \"string\", \"format\": \"date-time\"}}}",
    "examplePayload": "{\"invoiceId\": \"inv_123\", \"amount\": 500.00, \"paidAt\": \"2024-01-15T10:30:00Z\"}",
    "documentationUrl": "https://docs.example.com/webhooks/invoice-payment-succeeded"
  }'

Deprecating Event Types

When you need to remove an event type, deprecate it first to give consumers time to migrate:

curl -X PATCH "https://api.hookbase.app/api/organizations/{orgId}/event-types/{eventTypeId}" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "isDeprecated": true,
    "deprecatedMessage": "Use invoice.paid instead. This event will be removed on 2024-06-01."
  }'

Label Filtering

Labels allow you to send events to specific subsets of subscribers based on metadata.

Adding Labels to Events

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/send-event" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "eventType": "order.shipped",
    "payload": {"orderId": "ord_123", "carrier": "fedex"},
    "labels": {
      "region": "us-west",
      "priority": "high",
      "carrier": "fedex"
    }
  }'

Configuring Subscription Filters

Subscriptions can filter events based on labels:

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-subscriptions" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "endpointId": "wh_ep_xxx",
    "eventTypeId": "evt_type_xxx",
    "labelFilters": {
      "region": "us-west",
      "priority": ["high", "urgent"]
    },
    "labelFilterMode": "all"
  }'

Filter modes:

  • all: Event must match ALL filter conditions (AND logic)
  • any: Event must match ANY filter condition (OR logic)

Filter values:

  • Single value: Exact match required
  • Array of values: Event label must match any value in the array

Retry Behavior

Hookbase automatically retries failed webhook deliveries using exponential backoff.

Retry Schedule

AttemptDelay After Failure
1st retry10 seconds
2nd retry30 seconds
3rd retry2 minutes
4th retry10 minutes

After 5 total attempts (1 initial + 4 retries), the message is moved to the Dead Letter Queue (DLQ).

What Triggers a Retry

  • Network errors (timeout, connection refused, DNS failure)
  • HTTP 5xx responses (server errors)
  • HTTP 429 responses (rate limited)
  • Request timeouts

What Does NOT Retry

  • HTTP 2xx responses (success)
  • HTTP 4xx responses except 429 (client errors are not retried)

Circuit Breaker

Hookbase implements a circuit breaker pattern to prevent overwhelming failing endpoints.

Circuit States

StateDescriptionBehavior
ClosedNormal operationAll requests are sent
OpenEndpoint is failingRequests are skipped, message retried later
Half-OpenTesting recoveryProbe requests are sent to test endpoint health

Default Thresholds

ParameterDefaultDescription
Failure threshold5Consecutive failures to open circuit
Success threshold2Consecutive successes to close circuit
Cooldown period60 secondsTime before testing recovery

Configuring Circuit Breaker

Set custom thresholds when creating or updating an endpoint:

curl -X PATCH "https://api.hookbase.app/api/organizations/{orgId}/webhook-endpoints/{endpointId}" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "circuitFailureThreshold": 10,
    "circuitSuccessThreshold": 3,
    "circuitCooldownSeconds": 120
  }'

Manually Resetting Circuit Breaker

If you've fixed an endpoint issue, you can manually reset the circuit:

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/webhook-endpoints/{endpointId}/reset-circuit" \
  -H "Authorization: Bearer {token}"

Best Practices for Endpoint Implementation

1. Respond Quickly

Acknowledge webhooks within 5-10 seconds. Process events asynchronously if needed.

// Good: Acknowledge immediately, process async
app.post('/webhooks', async (req, res) => {
  // Verify signature
  verifySignature(req);
 
  // Queue for async processing
  await queue.add('processWebhook', req.body);
 
  // Respond immediately
  res.status(200).json({ received: true });
});

2. Implement Idempotency

Use the x-hookbase-id header to deduplicate webhooks:

app.post('/webhooks', async (req, res) => {
  const messageId = req.headers['x-hookbase-id'];
 
  // Check if already processed
  if (await redis.get(`webhook:${messageId}`)) {
    return res.status(200).json({ received: true, duplicate: true });
  }
 
  // Process webhook
  await processWebhook(req.body);
 
  // Mark as processed (with TTL for cleanup)
  await redis.set(`webhook:${messageId}`, '1', 'EX', 86400);
 
  res.status(200).json({ received: true });
});

3. Return Appropriate Status Codes

ScenarioStatus CodeHookbase Behavior
Success200-299Mark as delivered
Bad request (your fault)400-499 (except 429)Mark as failed, no retry
Rate limited429Retry with backoff
Server error500-599Retry with backoff
Timeout-Retry with backoff

4. Log Webhook Receipts

Maintain an audit trail for debugging:

app.post('/webhooks', async (req, res) => {
  const receipt = {
    messageId: req.headers['x-hookbase-id'],
    eventType: req.headers['x-hookbase-event-type'],
    timestamp: req.headers['x-hookbase-timestamp'],
    receivedAt: new Date().toISOString(),
    payload: req.body,
  };
 
  await db.webhookReceipts.create(receipt);
 
  // Process...
});

5. Handle Signature Verification Failures Gracefully

app.post('/webhooks', (req, res) => {
  try {
    verifySignature(req.body, req.headers, secret);
  } catch (error) {
    // Log for security monitoring
    console.error('Webhook signature verification failed:', {
      error: error.message,
      headers: req.headers,
      ip: req.ip,
    });
 
    // Return 401 but don't leak details
    return res.status(401).json({ error: 'Unauthorized' });
  }
 
  // Process verified webhook...
});

Monitoring and Debugging

Viewing Delivery Status

List recent messages and their delivery status:

curl "https://api.hookbase.app/api/organizations/{orgId}/outbound-messages?status=failed&limit=20" \
  -H "Authorization: Bearer {token}"

Viewing Delivery Attempts

See all attempts for a specific message:

curl "https://api.hookbase.app/api/organizations/{orgId}/outbound-messages/{messageId}/attempts" \
  -H "Authorization: Bearer {token}"

Checking DLQ

View messages that have exhausted all retries:

curl "https://api.hookbase.app/api/organizations/{orgId}/outbound-messages/dlq/messages" \
  -H "Authorization: Bearer {token}"

Retrying Failed Messages

Replay a failed message:

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/outbound-messages/{messageId}/replay" \
  -H "Authorization: Bearer {token}"

Retry a DLQ message:

curl -X POST "https://api.hookbase.app/api/organizations/{orgId}/outbound-messages/dlq/{messageId}/retry" \
  -H "Authorization: Bearer {token}"

Next Steps