Endpoints
Endpoints are HTTP URLs belonging to your customers that receive webhook events. Each endpoint subscribes to specific event types and has its own secret for signature verification.
The Endpoint Object
{
"id": "ep_4Bz5Z0sR1tM7oP3xD",
"application_id": "app_2Zy3X8qP9rK5mN1vB",
"url": "https://api.customer.com/webhooks",
"status": "active",
"event_types": ["user.created", "user.updated", "invoice.paid"],
"headers": {
"X-Customer-ID": "cust_123",
"X-Environment": "production"
},
"rate_limit_rps": 100,
"timeout_ms": 30000,
"secret": "whsec_a1b2c3d4e5f6...",
"secret_version": 1,
"failure_count": 0,
"disabled_at": null,
"created_at": "2026-01-18T10:30:00Z",
"updated_at": "2026-01-20T15:30:00Z"
}| Attribute | Type | Description |
|---|---|---|
id | string | Unique identifier (KSUID format) |
application_id | string | Application this endpoint belongs to |
url | string | HTTPS endpoint URL (max 2048 chars) |
status | enum | active, paused, or disabled |
event_types | array | Event types this endpoint subscribes to |
headers | object | Custom headers sent with each webhook |
rate_limit_rps | integer | Max requests per second (1-10000) |
timeout_ms | integer | Request timeout in milliseconds (1000-120000) |
secret | string | Signing secret (64 hex characters) |
failure_count | integer | Recent consecutive failures (circuit breaker) |
secret field is only returned when creating or retrieving a specific endpoint. It's hidden from list operations for security.Create Endpoint
Create a new endpoint to receive webhook events. The endpoint must use HTTPS and will be automatically assigned a unique signing secret.
POST /v1/endpointsRequest Body
{
"application_id": "app_2Zy3X8qP9rK5mN1vB",
"url": "https://api.customer.com/webhooks",
"event_types": ["user.created", "user.updated"],
"headers": {
"X-Customer-ID": "cust_123"
},
"rate_limit_rps": 50,
"timeout_ms": 15000
}| Parameter | Required | Description |
|---|---|---|
application_id | Yes | Application ID |
url | Yes | HTTPS URL (no localhost/private IPs) |
event_types | No | Array of event type names (default: []) |
headers | No | Custom headers object |
rate_limit_rps | No | Requests per second (default: 100) |
timeout_ms | No | Timeout in milliseconds (default: 30000) |
Response
{
"id": "ep_4Bz5Z0sR1tM7oP3xD",
"application_id": "app_2Zy3X8qP9rK5mN1vB",
"url": "https://api.customer.com/webhooks",
"status": "active",
"event_types": ["user.created", "user.updated"],
"headers": {
"X-Customer-ID": "cust_123"
},
"rate_limit_rps": 50,
"timeout_ms": 15000,
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
"secret_version": 1,
"failure_count": 0,
"created_at": "2026-01-20T15:30:00Z"
}secret is only returned once during creation. Store it securely - your customer will need it to verify webhook signatures.Code Examples
const response = await fetch('https://api.hookmesh.com/v1/endpoints', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HOOKMESH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
application_id: 'app_2Zy3X8qP9rK5mN1vB',
url: 'https://api.customer.com/webhooks',
event_types: ['user.created', 'user.updated'],
headers: {
'X-Customer-ID': 'cust_123'
},
rate_limit_rps: 50,
timeout_ms: 15000
})
});
const endpoint = await response.json();
console.log('Endpoint created:', endpoint.id);
console.log('Secret:', endpoint.secret);import requests
import os
response = requests.post(
'https://api.hookmesh.com/v1/endpoints',
headers={'Authorization': f'Bearer {os.environ.get("HOOKMESH_API_KEY")}'},
json={
'application_id': 'app_2Zy3X8qP9rK5mN1vB',
'url': 'https://api.customer.com/webhooks',
'event_types': ['user.created', 'user.updated'],
'headers': {
'X-Customer-ID': 'cust_123'
},
'rate_limit_rps': 50,
'timeout_ms': 15000
}
)
endpoint = response.json()
print(f'Endpoint created: {endpoint["id"]}')
print(f'Secret: {endpoint["secret"]}')package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
payload := map[string]interface{}{
"application_id": "app_2Zy3X8qP9rK5mN1vB",
"url": "https://api.customer.com/webhooks",
"event_types": []string{"user.created", "user.updated"},
"headers": map[string]string{
"X-Customer-ID": "cust_123",
},
"rate_limit_rps": 50,
"timeout_ms": 15000,
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST",
"https://api.hookmesh.com/v1/endpoints",
bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("HOOKMESH_API_KEY"))
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("Endpoint: %s\nSecret: %s\n", result["id"], result["secret"])
}<?php
$ch = curl_init('https://api.hookmesh.com/v1/endpoints');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . getenv('HOOKMESH_API_KEY'),
'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'application_id' => 'app_2Zy3X8qP9rK5mN1vB',
'url' => 'https://api.customer.com/webhooks',
'event_types' => ['user.created', 'user.updated'],
'headers' => [
'X-Customer-ID' => 'cust_123',
],
'rate_limit_rps' => 50,
'timeout_ms' => 15000,
]));
$response = curl_exec($ch);
$endpoint = json_decode($response, true);
echo "Endpoint: {$endpoint['id']}\n";
echo "Secret: {$endpoint['secret']}\n";
curl_close($ch);List Endpoints
Retrieve all endpoints for an application with filtering options.
GET /v1/endpoints?application_id={id}&status={status}Query Parameters
| Parameter | Required | Description |
|---|---|---|
application_id | Yes | Filter by application |
status | No | Filter by status (active, paused, disabled) |
page | No | Page number (default: 1) |
per_page | No | Items per page (default: 25, max: 100) |
Response
{
"data": [
{
"id": "ep_4Bz5Z0sR1tM7oP3xD",
"application_id": "app_2Zy3X8qP9rK5mN1vB",
"url": "https://api.customer.com/webhooks",
"status": "active",
"event_types": ["user.created", "user.updated"],
"failure_count": 0,
"created_at": "2026-01-20T15:30:00Z"
}
],
"pagination": {
"total": 5,
"page": 1,
"per_page": 25,
"total_pages": 1
}
}secret field is not included in list responses for security. Retrieve individual endpoints to view secrets.Retrieve Endpoint
Get detailed information about a specific endpoint, including its secret.
GET /v1/endpoints/{endpoint_id}Response
{
"id": "ep_4Bz5Z0sR1tM7oP3xD",
"application_id": "app_2Zy3X8qP9rK5mN1vB",
"url": "https://api.customer.com/webhooks",
"status": "active",
"event_types": ["user.created", "user.updated"],
"headers": {
"X-Customer-ID": "cust_123"
},
"rate_limit_rps": 50,
"timeout_ms": 15000,
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6",
"secret_version": 1,
"failure_count": 0,
"disabled_at": null,
"created_at": "2026-01-20T15:30:00Z",
"updated_at": "2026-01-20T15:30:00Z"
}Update Endpoint
Update an endpoint's configuration. All fields are optional.
PATCH /v1/endpoints/{endpoint_id}Request Body
{
"url": "https://api.customer.com/webhooks/v2",
"event_types": ["user.created", "user.updated", "user.deleted"],
"status": "paused",
"rate_limit_rps": 75,
"timeout_ms": 20000
}Status Values
| Status | Description |
|---|---|
active | Endpoint receives all subscribed webhooks |
paused | Temporarily disabled by user (no deliveries) |
disabled | Disabled by circuit breaker (automatic) |
Delete Endpoint
Soft delete an endpoint. No new webhooks will be delivered, but the endpoint can be restored.
DELETE /v1/endpoints/{endpoint_id}Response
{
"id": "ep_4Bz5Z0sR1tM7oP3xD",
"deleted": true,
"deleted_at": "2026-01-20T16:50:00Z"
}Rotate Secret
Generate a new signing secret for an endpoint. The old secret remains valid during a grace period to allow zero-downtime rotation.
POST /v1/endpoints/{endpoint_id}/rotate-secretResponse
{
"id": "ep_4Bz5Z0sR1tM7oP3xD",
"secret": "whsec_x1y2z3a4b5c6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6",
"secret_version": 2,
"old_secret_expires_at": "2026-01-21T16:00:00Z"
}Rotation Process
// Step 1: Rotate the secret
const rotateResponse = await fetch(
'https://api.hookmesh.com/v1/endpoints/ep_xyz/rotate-secret',
{
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
);
const { secret: newSecret, old_secret_expires_at } = await rotateResponse.json();
// Step 2: Update your verification code to try both secrets
function verifySignature(payload, signature, timestamp) {
// Try new secret first
if (verify(payload, signature, timestamp, newSecret)) {
return true;
}
// Fall back to old secret during transition period
if (verify(payload, signature, timestamp, oldSecret)) {
return true;
}
return false;
}
// Step 3: After 24 hours, remove old secret from your code
// The old secret is automatically invalidatedSend Test Webhook
Send a test webhook to an endpoint to verify it's working correctly.
POST /v1/endpoints/{endpoint_id}/testRequest Body (Optional)
{
"payload": {
"type": "test",
"message": "Testing webhook delivery",
"timestamp": "2026-01-20T15:30:00Z"
}
}Response
{
"job_id": "job_5nM8pQ1rK3vL9xB",
"status": "queued",
"message": "Test webhook queued for delivery"
}Reset Circuit Breaker
Manually reset the circuit breaker for a disabled endpoint after fixing the issue.
POST /v1/endpoints/{endpoint_id}/reset-circuitResponse
{
"id": "ep_4Bz5Z0sR1tM7oP3xD",
"status": "active",
"failure_count": 0,
"message": "Circuit breaker reset, endpoint reactivated"
}Common Patterns
Endpoint Subscription Management
Allow your customers to choose which events they want to receive:
async function updateCustomerWebhookSubscriptions(customerId, selectedEvents) {
// Find the customer's endpoint
const endpoints = await fetch(
`https://api.hookmesh.com/v1/endpoints?application_id=${appId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
).then(r => r.json());
const endpoint = endpoints.data.find(ep =>
ep.headers['X-Customer-ID'] === customerId
);
// Update their event subscriptions
await fetch(`https://api.hookmesh.com/v1/endpoints/${endpoint.id}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
event_types: selectedEvents
})
});
}Custom Headers for Context
Use custom headers to pass additional context with each webhook:
{
"headers": {
"X-Customer-ID": "cust_123",
"X-Tenant-ID": "tenant_abc",
"X-Environment": "production",
"X-Region": "us-west-2",
"X-Account-Tier": "enterprise"
}
}These headers are sent with every webhook, making it easy for your customers to route and process events.
Rate Limiting Strategy
// Set conservative rate limits for new customers
const newCustomerEndpoint = {
rate_limit_rps: 10,
timeout_ms: 30000
};
// Increase limits for enterprise customers
const enterpriseEndpoint = {
rate_limit_rps: 500,
timeout_ms: 15000 // Faster timeout for reliable endpoints
};Best Practices
- Always use HTTPS: Hook Mesh enforces HTTPS for security
- Set appropriate timeouts: Match your customer's response time (15-30 seconds typical)
- Use custom headers: Include customer/tenant IDs for easier routing
- Test before production: Use the test endpoint feature to verify configuration
- Rotate secrets regularly: Update secrets every 90 days for compliance
- Monitor failure counts: High failure counts indicate customer endpoint issues
- Document the secret: Ensure customers save their secret during endpoint creation
Next Steps
- Define event types for your webhooks
- Send webhook jobs to your endpoints
- Learn how to verify signatures in your customer's code
- Understand the circuit breaker mechanism