Base URL: http://localhost:5100/api/v1
Authentication: Authorization: Bearer {api_key}
Content-Type: application/json
All API endpoints (except dashboard auth) require an Application API key:
Authorization: Bearer whe_app1a2b3_xK9mNpQrStUvWxYz1234567890abcdefErrors:
401 Unauthorized— Missing or invalid API key403 Forbidden— API key valid but insufficient permissions
{
"data": { ... },
"meta": {
"requestId": "req_abc123"
}
}{
"data": [ ... ],
"meta": {
"requestId": "req_abc123",
"pagination": {
"page": 1,
"pageSize": 20,
"totalCount": 156,
"totalPages": 8,
"hasNext": true,
"hasPrev": false
}
}
}{
"error": {
"code": "VALIDATION_ERROR",
"message": "The endpoint URL is not valid.",
"details": [
{ "field": "url", "message": "Must be a valid HTTPS URL." }
]
},
"meta": {
"requestId": "req_abc123"
}
}| HTTP Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR |
Request body validation failed |
| 401 | UNAUTHORIZED |
Missing or invalid API key |
| 404 | NOT_FOUND |
Resource does not exist |
| 409 | CONFLICT |
Duplicate (e.g., idempotency key) |
| 422 | UNPROCESSABLE |
Request understood but cannot be processed |
| 429 | RATE_LIMITED |
Too many requests |
| 500 | INTERNAL_ERROR |
Unexpected server error |
GET /health
GET /api/v1/health
No authentication required.
Response: 200 OK
{
"status": "healthy"
}Applications are managed via the dashboard (cookie auth, not API key).
POST /api/v1/applications
Request:
{
"name": "My SaaS App"
}Response: 201 Created
{
"data": {
"id": "...",
"name": "My SaaS App",
"apiKey": "whe_abc123_xK9m...",
"signingSecret": "base64-encoded-secret",
"isActive": true,
"createdAt": "2026-02-26T14:30:00Z"
}
}Note: apiKey is only returned on creation. Store it securely — it cannot be retrieved again.
GET /api/v1/applications
?page=1
&pageSize=20
GET /api/v1/applications/{applicationId}
PUT /api/v1/applications/{applicationId}
DELETE /api/v1/applications/{applicationId}
POST /api/v1/applications/{applicationId}/rotate-key
Generates a new API key. The old key is immediately invalidated.
POST /api/v1/applications/{applicationId}/rotate-secret
Generates a new HMAC signing secret.
POST /api/v1/event-types
Request:
{
"name": "order.created",
"description": "Fired when a new order is placed"
}Response: 201 Created
{
"data": {
"id": "evt_abc123",
"name": "order.created",
"description": "Fired when a new order is placed",
"createdAt": "2026-02-26T14:30:00Z"
}
}GET /api/v1/event-types
?page=1
&pageSize=20
&includeArchived=false
Response: 200 OK (paginated list)
GET /api/v1/event-types/{eventTypeId}
PUT /api/v1/event-types/{eventTypeId}
DELETE /api/v1/event-types/{eventTypeId}
Note: Soft delete (archive). Existing messages referencing this type are unaffected.
POST /api/v1/endpoints
Request:
{
"url": "https://api.customer.com/webhooks",
"description": "Customer A production webhook",
"filterEventTypes": ["evt_abc123", "evt_def456"],
"customHeaders": {
"X-Custom-Auth": "secret123"
},
"metadata": {
"customerId": "cust_001",
"environment": "production"
}
}Response: 201 Created
{
"data": {
"id": "ep_xyz789",
"url": "https://api.customer.com/webhooks",
"description": "Customer A production webhook",
"status": "active",
"filterEventTypes": ["evt_abc123", "evt_def456"],
"customHeaders": { "X-Custom-Auth": "***" },
"metadata": { "customerId": "cust_001", "environment": "production" },
"health": {
"state": "healthy",
"consecutiveFailures": 0,
"lastSuccessAt": null,
"lastFailureAt": null
},
"createdAt": "2026-02-26T14:30:00Z"
}
}Notes:
- If
filterEventTypesis empty or omitted, endpoint receives ALL event types. customHeadersvalues are masked in GET responses (***).
GET /api/v1/endpoints
?page=1
&pageSize=20
&status=active (active | disabled)
GET /api/v1/endpoints/{endpointId}
Includes health status.
PUT /api/v1/endpoints/{endpointId}
POST /api/v1/endpoints/{endpointId}/disable
POST /api/v1/endpoints/{endpointId}/enable
DELETE /api/v1/endpoints/{endpointId}
GET /api/v1/endpoints/{endpointId}/stats
?period=24h (1h | 24h | 7d | 30d)
Response: 200 OK
{
"data": {
"endpointId": "ep_xyz789",
"period": "24h",
"totalAttempts": 1250,
"successful": 1230,
"failed": 20,
"successRate": 98.4,
"avgLatencyMs": 145,
"p95LatencyMs": 320
}
}POST /api/v1/messages
This is the primary API. Your application calls this when an event occurs.
Request:
{
"eventType": "order.created",
"payload": {
"orderId": "ord_abc123",
"amount": 99.99,
"currency": "TRY",
"customer": {
"id": "cust_001",
"email": "user@example.com"
}
},
"eventId": "evt_unique_001",
"idempotencyKey": "idem_order_abc123_created"
}Response: 202 Accepted
{
"data": {
"messageIds": [
"msg_aaa111",
"msg_bbb222"
],
"endpointCount": 2,
"eventType": "order.created"
}
}Notes:
- Returns
202 Accepted(not201 Created) because delivery is async. messageIds— one per subscribed endpoint (fan-out).eventId— optional client-side event identifier for correlation.idempotencyKey— optional. If provided, duplicate sends with same key return the original response (within 24h window).
POST /api/v1/messages/batch
Request:
{
"messages": [
{
"eventType": "order.created",
"payload": { "orderId": "ord_001" },
"eventId": "evt_001"
},
{
"eventTypeId": "0f3e8c63-4fef-4f4f-8f2f-2df0b0d61c11",
"payload": { "orderId": "ord_002" }
}
]
}Response: 202 Accepted
{
"data": {
"totalEvents": 2,
"acceptedEvents": 2,
"rejectedEvents": 0,
"totalEnqueuedMessages": 4,
"results": [
{
"index": 0,
"success": true,
"eventType": "order.created",
"endpointCount": 2,
"messageIds": ["..."]
}
]
}
}POST /api/v1/messages/replay
Request:
{
"eventType": "order.created",
"from": "2026-02-25T00:00:00Z",
"to": "2026-02-26T23:59:59Z",
"statuses": ["delivered", "failed"],
"maxMessages": 100
}Response: 202 Accepted
{
"data": {
"sourceCount": 18,
"replayedCount": 18,
"messageIds": ["..."],
"eventType": "order.created",
"endpointId": null,
"from": "2026-02-25T00:00:00Z",
"to": "2026-02-26T23:59:59Z",
"maxMessages": 100,
"statuses": ["delivered", "failed"]
}
}GET /api/v1/messages/{messageId}
Response: 200 OK
{
"data": {
"id": "msg_aaa111",
"eventType": "order.created",
"endpoint": {
"id": "ep_xyz789",
"url": "https://api.customer.com/webhooks"
},
"payload": { ... },
"status": "delivered",
"attemptCount": 1,
"maxRetries": 7,
"scheduledAt": "2026-02-26T14:30:00Z",
"deliveredAt": "2026-02-26T14:30:00.342Z",
"createdAt": "2026-02-26T14:30:00Z"
}
}GET /api/v1/messages
?page=1
&pageSize=20
&status=delivered (pending | sending | delivered | failed | deadletter)
&eventTypeId={uuid}
&endpointId={uuid}
&before=2026-02-26T23:59:59Z
&after=2026-02-25T00:00:00Z
POST /api/v1/messages/{messageId}/retry
Resets message status to pending and schedules immediate delivery. Works for failed and deadletter messages.
Response: 200 OK
{
"data": {
"messageId": "msg_aaa111",
"status": "pending",
"scheduledAt": "2026-02-26T15:00:00Z"
}
}GET /api/v1/messages/{messageId}/attempts
?page=1
&pageSize=20
Response: 200 OK
{
"data": [
{
"id": "att_001",
"attemptNumber": 1,
"status": "failed",
"statusCode": 500,
"responseBody": "{\"error\":\"internal server error\"}",
"latencyMs": 2340,
"createdAt": "2026-02-26T14:30:00Z"
},
{
"id": "att_002",
"attemptNumber": 2,
"status": "success",
"statusCode": 200,
"responseBody": "{\"received\":true}",
"latencyMs": 145,
"createdAt": "2026-02-26T14:30:05Z"
}
]
}These endpoints power the React dashboard. Authenticated via dashboard session cookie (not API key).
GET /api/v1/dashboard/overview
Response:
{
"data": {
"last24h": {
"totalMessages": 12500,
"delivered": 12350,
"failed": 120,
"pending": 30,
"deadLetter": 0,
"successRate": 98.8,
"avgLatencyMs": 156
},
"endpoints": {
"total": 25,
"healthy": 22,
"degraded": 2,
"failed": 1,
"disabled": 0
},
"queueDepth": 30
}
}GET /api/v1/dashboard/timeline
?period=24h (1h | 24h | 7d | 30d)
&interval=1h (5m | 1h | 1d)
Response:
{
"data": {
"buckets": [
{ "timestamp": "2026-02-26T00:00:00Z", "delivered": 520, "failed": 5 },
{ "timestamp": "2026-02-26T01:00:00Z", "delivered": 480, "failed": 3 },
...
]
}
}GET /api/v1/dashboard/endpoints
?appId={uuid}
&status=active
&page=1
&pageSize=20
POST /api/v1/dashboard/endpoints
Request body same as POST /api/v1/endpoints but with additional appId field.
PUT /api/v1/dashboard/endpoints/{endpointId}
POST /api/v1/dashboard/endpoints/{endpointId}/disable
POST /api/v1/dashboard/endpoints/{endpointId}/enable
DELETE /api/v1/dashboard/endpoints/{endpointId}
GET /api/v1/dashboard/event-types
?appId={uuid}
&includeArchived=false
POST /api/v1/dashboard/event-types
PUT /api/v1/dashboard/event-types/{eventTypeId}
DELETE /api/v1/dashboard/event-types/{eventTypeId}
GET /api/v1/dashboard/messages
?appId={uuid}
&status=delivered
&endpointId={uuid}
&eventType=order.created
&after=2026-02-25T00:00:00Z
&before=2026-02-26T23:59:59Z
&page=1
&pageSize=20
GET /api/v1/dashboard/messages/{messageId}
Returns message with attempts included.
POST /api/v1/dashboard/messages/send
POST /api/v1/dashboard/messages/{messageId}/retry
POST /api/v1/auth/login
Request:
{
"email": "admin@example.com",
"password": "changeme"
}Response: 200 OK + HttpOnly session cookie
POST /api/v1/auth/logout
GET /api/v1/auth/me
Every webhook delivery includes these standard headers:
| Header | Example | Description |
|---|---|---|
Content-Type |
application/json |
Always JSON |
User-Agent |
WebhookEngine/1.0 |
Identifies sender |
webhook-id |
msg_aaa111 |
Unique message identifier |
webhook-timestamp |
1740600000 |
Unix timestamp (seconds) |
webhook-signature |
v1,K7gNU3sdo+OL... |
HMAC-SHA256 signature |
Plus any custom headers configured on the endpoint.
| Scope | Limit | Header |
|---|---|---|
| Per API Key | 1000 requests/minute | X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset |
| Message sending | 100 messages/second per app | Same headers |
When rate limited: 429 Too Many Requests with Retry-After header.
var client = new WebhookEngineClient("whe_app1a2b3_xK9m...", "http://localhost:5100");
// Send a webhook
await client.Messages.SendAsync(new SendMessageRequest
{
EventType = "order.created",
Payload = new { OrderId = "ord_abc123", Amount = 99.99m },
IdempotencyKey = "idem_order_abc123_created"
});
// List endpoints
var endpoints = await client.Endpoints.ListAsync(page: 1, pageSize: 20);
// Retry a failed message
await client.Messages.RetryAsync("msg_aaa111");TypeScript SDK is planned for Phase 2 and is not published yet. Use the REST API examples below (or the .NET SDK above) until npm package release.
# Send a webhook
curl -X POST http://localhost:5100/api/v1/messages \
-H "Authorization: Bearer whe_app1a2b3_xK9m..." \
-H "Content-Type: application/json" \
-d '{
"eventType": "order.created",
"payload": {"orderId": "ord_abc123", "amount": 99.99}
}'