Version: 0.4.0
Example Base URL: https://api.example.com
This document describes the public API surface for a generic multi-tenant webhook ingestion backend. All domains, tenants, contacts, and payloads shown here are safe examples.
All management endpoints, except health checks and webhook ingestion, require an admin token header:
X-Admin-Token: <your-admin-token>Webhook endpoints support either:
- HMAC-SHA256 request signing
- tenant-specific tokenized webhook URLs
Prometheus metrics are protected in production with:
X-Metrics-Token: <your-metrics-token>Basic liveness check. Returns 200 if the process is running.
Auth: None
{"ok": true}Liveness probe. Functionally equivalent to /healthz.
Auth: None
Readiness check. Verifies required dependencies such as database and Redis connectivity.
Auth: None
{
"ok": true,
"checks": {
"database": "healthy",
"redis": "healthy"
}
}Tenant endpoints are used to manage isolated customer/workspace accounts.
Creates a new tenant and returns a unique tokenized webhook URL.
Auth: X-Admin-Token
{
"name": "Acme Demo Workspace",
"plan": "basic"
}Response:
{
"id": "uuid",
"name": "Acme Demo Workspace",
"plan": "basic",
"created_at": "2026-03-15T12:00:00Z",
"webhook_url": "https://api.example.com/api/v1/webhooks/lead-event/t/<token>"
}Lists all tenants.
Auth: X-Admin-Token
Returns a single tenant.
Auth: X-Admin-Token
Updates tenant metadata.
Auth: X-Admin-Token
{
"name": "Updated Demo Workspace",
"plan": "pro"
}Deletes a tenant and its related data according to configured database cascade rules.
Auth: X-Admin-Token
Webhook endpoints receive signed or token-authenticated external events from automation tools, CRMs, internal systems, or other applications.
Ingests an external event with HMAC signature verification.
Auth: HMAC-SHA256 signature
Required headers:
X-Signature: <hmac-hex-digest>
X-Timestamp: <unix-timestamp>
X-Tenant-ID: <tenant-id>Signature format:
HMAC-SHA256(secret, "<timestamp>.<raw-body>")
Example body:
{
"tenant_id": "uuid",
"lead_id": "entity-123",
"event_id": "evt-001",
"event_type": "lead_created",
"source": "automation-platform",
"timestamp": "2026-03-15T12:00:00Z",
"data": {
"campaign_id": "campaign-1",
"campaign_name": "Demo Campaign",
"first_name": "Alex",
"last_name": "Morgan",
"email": "alex@example.com",
"phone": "+46700000000"
}
}Response:
{"detail": "Lead event ingested (corr=<request-id>)"}Duplicate events are ignored idempotently:
{"detail": "Duplicate event ignored (event_id=evt-001, corr=<request-id>)"}Token-based webhook endpoint. The token identifies the tenant, so tenant_id can be omitted from the request body.
Auth: Token in URL
Events are normalized internally. The current implementation supports lifecycle-style lead/contact, messaging, workflow, conversion, and opt-out events.
| Category | Accepted values | Canonical |
|---|---|---|
| Entity created | lead.created, lead_created, created |
lead_created |
| Entity imported | lead.imported, imported |
lead_imported |
| Contacted | contacted |
contacted |
| Email sent | email.sent, email_sent |
email_sent |
| SMS sent | sms.sent, sms_sent |
sms_sent |
| Email delivered | email.delivered, delivered |
email_delivered |
| SMS delivered | sms.delivered, sms_delivered |
sms_delivered |
| Engaged | engaged |
engaged |
| Email opened | email.opened, opened, open |
email_opened |
| Link clicked | clicked, click, link.clicked |
clicked |
| Replied | replied, reply |
replied |
| Email bounced | email.bounced, bounced, bounce |
email_bounced |
| SMS failed | sms.failed, sms_failed |
sms_failed |
| Email failed | email.failed, email_failed |
email_failed |
| Converted | converted, convert |
converted |
| Deal won | deal.won, deal_won, won |
deal_won |
| Sale | sale |
sale |
| Workflow processed | workflow.processed |
workflow_processed |
| Workflow success | workflow.success, success |
workflow_success |
| Workflow failed | workflow.failed |
workflow_failed |
| Opt-out all | opt_out, unsubscribe, opt.out |
opt_out_all |
| Opt-out SMS | opt_out_sms, stop, sms.opt_out |
opt_out_sms |
| Opt-out email | opt_out_email, email.unsubscribe |
opt_out_email |
Each accepted event can trigger the following processing flow:
- Store the event idempotently by
event_id - Upsert the related contact/entity record
- Update lifecycle status without moving backward
- Update campaign/workflow-style aggregate statistics when present
- Update revenue/conversion snapshots when present
- Create message logs for email/SMS lifecycle events
- Handle opt-out events
- Store failures in the dead-letter queue for retry
If timestamp is missing, the system falls back to occurred_at or created_at.
If data is empty or missing, non-standard top-level fields are packed into data automatically.
All contact endpoints are scoped to a tenant:
/api/v1/tenants/{tenant_id}/contacts
Lists contacts with filtering and search.
Auth: X-Admin-Token
| Param | Type | Description |
|---|---|---|
| status | string | Filter by lifecycle status |
| source | string | Filter by source system |
| is_member | bool | Filter by membership flag |
| opted_out | bool | true = only opt-outs, false = only active contacts |
| search | string | Free text search over name, email, or phone |
| limit | int | 1-500, default 50 |
| offset | int | default 0 |
Returns contact count, optionally filtered by status.
Auth: X-Admin-Token
{"tenant_id": "uuid", "total": 42, "status_filter": null}Returns full contact details.
Auth: X-Admin-Token
{
"id": "uuid",
"tenant_id": "uuid",
"external_contact_id": "entity-123",
"first_name": "Alex",
"last_name": "Morgan",
"email": "alex@example.com",
"phone": "+46700000000",
"normalized_phone": "+46700000000",
"registration_number": null,
"source": "automation-platform",
"status": "converted",
"member_status": "non_member",
"is_member": false,
"opted_out_sms": false,
"opted_out_email": false,
"opt_out_reason": null,
"opted_out_at": null,
"consent_sms": false,
"consent_email": false,
"tags": [],
"metadata_json": {},
"last_message_sent_at": "2026-03-15T12:01:00Z",
"last_engagement_at": "2026-03-15T12:02:00Z",
"last_seen_at": "2026-03-15T12:02:00Z",
"created_at": "2026-03-15T12:00:00Z",
"updated_at": "2026-03-15T12:02:00Z"
}Creates a contact manually.
Auth: X-Admin-Token
{
"first_name": "Jamie",
"last_name": "Taylor",
"email": "jamie@example.com",
"phone": "+46700000001",
"source": "api",
"status": "new"
}Errors: 409 if email or phone already exists for the tenant.
Partially updates a contact. Only included fields are changed.
Auth: X-Admin-Token
{
"first_name": "Jamie A.",
"status": "contacted"
}Opts out a contact from a channel.
Auth: X-Admin-Token
{
"channel": "sms",
"source": "user_reply",
"reason": "User requested unsubscribe"
}channel: sms, email, or all
Reverses an opt-out.
Auth: X-Admin-Token
{
"channel": "sms",
"source": "admin_action"
}Returns opt-out history for a contact.
Auth: X-Admin-Token
Query params: limit, offset
All message log endpoints are scoped to:
/api/v1/tenants/{tenant_id}/message-logs
Logs a sent or received message manually. The related contact must already exist.
Auth: X-Admin-Token
{
"contact_id": "uuid",
"channel": "sms",
"direction": "outbound",
"provider": "example-provider",
"provider_message_id": "ext-msg-123",
"body": "This is a safe demo message.",
"status": "sent"
}channel: sms or email
direction: outbound or inbound
status: queued, sent, delivered, failed, opened, clicked, replied, bounced
Lists message logs with filters.
Auth: X-Admin-Token
| Param | Type | Description |
|---|---|---|
| contact_id | string | Filter by contact |
| campaign_id | string | Filter by campaign |
| workflow_id | string | Filter by workflow |
| channel | string | sms or email |
| direction | string | outbound or inbound |
| status | string | Filter by status |
| limit | int | 1-500, default 50 |
| offset | int | default 0 |
Returns a single message log.
Auth: X-Admin-Token
All endpoints are scoped to:
/api/v1/tenants/{tenant_id}/failed-events
Lists failed events, newest first.
Auth: X-Admin-Token
Query params: limit, offset
Returns the number of failed events.
Auth: X-Admin-Token
Returns a single failed event with full payload.
Auth: X-Admin-Token
Re-processes a failed event. On success, it is removed from the queue. On failure, retry_count is incremented.
Auth: X-Admin-Token
Permanently removes a failed event from the queue.
Auth: X-Admin-Token
Returns dashboard-style overview metrics.
Auth: X-Admin-Token
Query params: tenant_id optional. Omit for global overview.
{
"tenant_id": "uuid",
"total_campaigns": 3,
"total_workflows": 2,
"total_lead_events": 150,
"total_revenue": "45000.00",
"currency": "SEK",
"total_contacts": 42,
"contact_status_breakdown": {
"new": 10,
"contacted": 15,
"engaged": 12,
"converted": 5,
"other": 0
},
"total_opted_out_sms": 3,
"total_opted_out_email": 1,
"total_messages": 89,
"total_messages_sent": 45,
"total_messages_delivered": 40,
"total_messages_failed": 2,
"total_failed_events": 0,
"last_event_at": "2026-03-15T14:30:00Z",
"last_revenue_snapshot_at": "2026-03-15T14:00:00Z"
}Lists campaign-style statistics.
Auth: X-Admin-Token
Query params: tenant_id, limit, offset
Lists workflow statistics.
Auth: X-Admin-Token
Query params: tenant_id, limit, offset
Lists raw ingested events.
Auth: X-Admin-Token
Query params: tenant_id, event_type, limit, offset
Lists conversion/revenue snapshots.
Auth: X-Admin-Token
Query params: tenant_id, limit, offset
These endpoints provide tenant-filtered views:
GET /api/v1/tenants/{tenant_id}/campaign-statsGET /api/v1/tenants/{tenant_id}/workflow-statsGET /api/v1/tenants/{tenant_id}/lead-eventsGET /api/v1/tenants/{tenant_id}/revenue-snapshots
Prometheus-compatible metrics endpoint.
Auth: X-Metrics-Token header in production
All errors follow the same general format:
{
"detail": "Error message",
"request_id": "uuid"
}| Code | Meaning |
|---|---|
| 401 | Missing or invalid authentication |
| 404 | Resource not found |
| 409 | Conflict, such as duplicate email or phone |
| 413 | Payload too large |
| 422 | Validation error |
| 429 | Rate limited |
| 500 | Internal server error |
| Endpoint group | Limit |
|---|---|
| Health checks | 100/min default |
| Tenant CRUD | 10-30/min |
| Webhooks | 60/min |
| Contacts read | 60/min |
| Contacts write | 30/min |
| Message logs read | 60/min |
| Message logs write | 120/min |
| Metrics overview | 30/min |
| Metrics lists | 60/min |
| Failed events | 60/min |
| Failed events retry/delete | 10/min |
| Auth register | 5/min |
Rate limiting is per tenant when X-Tenant-ID or JWT tenant context is available, with IP fallback.