Skip to main content

Webhooks

Base URL: https://events.pana.space/api

Configure outbound webhooks to receive real-time notifications when events happen in your workspace.

Requires: workspace admin role.

Base path:

/api/v1/{workspace_id}/developers/webhooks/

List webhooks​

GET /api/v1/{workspace_id}/developers/webhooks/
Authorization: Bearer <jwt>
[
{
"id": "uuid",
"target_url": "https://example.com/webhooks/mathiverse",
"events": ["lead.created", "task.assigned"],
"is_active": true,
"created_at": "2026-06-01T09:00:00Z"
}
]

Create webhook​

POST /api/v1/{workspace_id}/developers/webhooks/
Authorization: Bearer <jwt>
Content-Type: application/json

{
"target_url": "https://example.com/webhooks/mathiverse",
"events": ["lead.created", "lead.updated", "task.assigned"]
}

At least one event is required. Returns 201 with webhook object.


Update webhook​

PATCH /api/v1/{workspace_id}/developers/webhooks/{webhook_id}/
Authorization: Bearer <jwt>
Content-Type: application/json

{
"target_url": "https://example.com/new-url",
"events": ["expense.submitted"]
}

Delete webhook​

DELETE /api/v1/{workspace_id}/developers/webhooks/{webhook_id}/

Soft-deactivates the webhook (204 No Content).


List available event types​

GET /api/v1/{workspace_id}/developers/webhook-events/
Authorization: Bearer <jwt>
[
{ "id": "lead.created", "label": "Lead created" },
{ "id": "lead.updated", "label": "Lead updated" },
{ "id": "event.updated", "label": "Event updated" },
{ "id": "demo_booking.updated", "label": "Demo booking updated" },
{ "id": "expense.submitted", "label": "Expense submitted" },
{ "id": "task.assigned", "label": "Task assigned" }
]

Supported event types​

EventDescription
lead.createdNew lead captured
lead.assignedLead assigned (also matches lead.updated subscriptions)
lead.status_changedLead status changed
lead.updatedLead updated
event.updatedEvent updated
event.cancelledEvent cancelled
demo_booking.createdDemo booking created
demo_booking.status_changedDemo status changed
demo_booking.updatedDemo booking updated
expense.createdExpense created
expense.status_changedExpense status changed
expense.submittedExpense submitted
task.assignedTask assigned
task.status_changedTask status changed
admin.member_joinedMember joined workspace
admin.role_changedMember role changed

Aliases: subscribing to lead.updated also receives lead.status_changed and lead.assigned. Similar aliases exist for demo bookings and expenses.


Delivery payload​

POST to your target_url:

{
"id": "delivery-uuid",
"type": "lead.created",
"created_at": "2026-06-20T10:00:00.000000+00:00",
"data": {
"lead_id": "…",
"workspace_id": "…"
}
}

Headers:

HeaderValue
Content-Typeapplication/json
User-AgentMathiverse-Webhooks/1.0
X-Webhook-EventEvent type (e.g. lead.created)
X-Webhook-Signaturesha256=<hmac_hex>

Verify signature​

import hashlib
import hmac

def verify(secret: str, body: bytes, signature_header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header)

The signing secret is generated server-side when the webhook is created. Retrieve it from Django admin or database if needed — it is not currently returned in the API response.


Retries​

Deliveries run asynchronously via Celery. Failed deliveries (non-2xx or network error) retry up to 3 times with exponential backoff.


When webhooks fire​

Webhooks are dispatched from the same notification pipeline as in-app notifications — when app code calls emit_notification_event() (lead create, task assign, expense submit, etc.).

Requires: Celery worker + Redis for async delivery.