Skip to content

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.

bash
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.

bash
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.

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

json
{
  "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.

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

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

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

json
{
  "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
bash
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:

bash
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

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

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

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

bash
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.

javascript
// 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:

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

javascript
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

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

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

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

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

Retrying Failed Messages

Replay a failed message:

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

Retry a DLQ message:

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

Next Steps

Released under the MIT License.