> For the complete documentation index, see [llms.txt](https://boundaryai.gitbook.io/boundaryai-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://boundaryai.gitbook.io/boundaryai-docs/api-and-webhooks/webhooks-1.md).

# Webhooks

Receive real-time notifications when events occur in BAI Analytics. Deliveries are signed with HMAC-SHA256 in the `X-Boundary-Signature` header; the event type is in `X-Boundary-Event`.

## Create a webhook

> Register an endpoint to receive real-time event notifications. The signing secret is returned once on creation — save it immediately, it cannot be retrieved again. Subscribable events: \`content.pushed\`, \`survey.created\`, \`survey.published\`, \`series.created\`.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Receive real-time notifications when events occur in BAI Analytics. Deliveries are signed with HMAC-SHA256 in the `X-Boundary-Signature` header; the event type is in `X-Boundary-Event`."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Clerk-issued JWT. Validated by the before_app_request hook in src/boundaryai_auth/routes/authentication.py. Token is expected in the Authorization header as `Bearer <jwt>`."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Opaque client-supplied key for safe POST retries. Repeated requests with the same key within a 24-hour window replay the original response instead of executing the mutation twice. Reusing the same key with a DIFFERENT request body returns 409 `IDEMPOTENCY_ERROR`. Backed by ``input_api_idempotency_keys`` in Postgres.","schema":{"type":"string","minLength":1,"maxLength":255}}},"schemas":{"CreateWebhookRequest":{"properties":{"name":{"maxLength":255,"minLength":1,"title":"Name","type":"string"},"url":{"description":"HTTPS endpoint to POST events to","title":"Url","type":"string"},"events":{"description":"One or more WebhookEventType values","items":{"type":"string"},"minItems":1,"title":"Events","type":"array"}},"required":["name","url","events"],"title":"CreateWebhookRequest","type":"object"},"CreateWebhookResponse":{"properties":{"webhook":{"$ref":"#/components/schemas/WebhookDict"},"secret":{"description":"HMAC secret shown once. Returned again only via regenerate-secret.","title":"Secret","type":"string"},"message":{"title":"Message","type":"string"}},"required":["webhook","secret","message"],"title":"CreateWebhookResponse","type":"object"},"WebhookDict":{"additionalProperties":true,"description":"Public representation of a configured webhook subscription.","properties":{"id":{"title":"Id","type":"integer"},"org_id":{"title":"Org Id","type":"integer"},"name":{"title":"Name","type":"string"},"url":{"title":"Url","type":"string"},"events":{"items":{"type":"string"},"title":"Events","type":"array"},"is_active":{"title":"Is Active","type":"boolean"},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Created At"},"last_delivery_at":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Last Delivery At"}},"required":["id","org_id","name","url","events","is_active"],"title":"WebhookDict","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"},"ContentPushedEvent":{"additionalProperties":false,"properties":{"event":{"const":"content.pushed","title":"Event","type":"string"},"timestamp":{"description":"ISO-8601 UTC timestamp","title":"Timestamp","type":"string"},"data":{"$ref":"#/components/schemas/ContentPushedData"}},"required":["event","timestamp","data"],"title":"ContentPushedEvent","type":"object"},"ContentPushedData":{"properties":{"survey_series_id":{"title":"Survey Series Id","type":"integer"},"survey_id":{"title":"Survey Id","type":"integer"},"question_id":{"title":"Question Id","type":"integer"},"original_survey_id":{"title":"Original Survey Id","type":"integer"},"original_question_id":{"title":"Original Question Id","type":"integer"},"lines_pushed":{"minimum":0,"title":"Lines Pushed","type":"integer"},"api_key_id":{"title":"Api Key Id","type":"integer"}},"required":["survey_series_id","survey_id","question_id","original_survey_id","original_question_id","lines_pushed","api_key_id"],"title":"ContentPushedData","type":"object"},"SeriesCreatedEvent":{"additionalProperties":false,"properties":{"event":{"const":"series.created","title":"Event","type":"string"},"timestamp":{"description":"ISO-8601 UTC timestamp","title":"Timestamp","type":"string"},"data":{"$ref":"#/components/schemas/SeriesCreatedData"}},"required":["event","timestamp","data"],"title":"SeriesCreatedEvent","type":"object"},"SeriesCreatedData":{"properties":{"survey_series_id":{"title":"Survey Series Id","type":"integer"},"name":{"title":"Name","type":"string"},"api_key_id":{"title":"Api Key Id","type":"integer"}},"required":["survey_series_id","name","api_key_id"],"title":"SeriesCreatedData","type":"object"},"SurveyCreatedEvent":{"additionalProperties":false,"properties":{"event":{"const":"survey.created","title":"Event","type":"string"},"timestamp":{"description":"ISO-8601 UTC timestamp","title":"Timestamp","type":"string"},"data":{"$ref":"#/components/schemas/SurveyCreatedData"}},"required":["event","timestamp","data"],"title":"SurveyCreatedEvent","type":"object"},"SurveyCreatedData":{"properties":{"survey_series_id":{"title":"Survey Series Id","type":"integer"},"survey_id":{"title":"Survey Id","type":"integer"},"survey_title":{"title":"Survey Title","type":"string"},"questions_created":{"minimum":0,"title":"Questions Created","type":"integer"},"api_key_id":{"title":"Api Key Id","type":"integer"}},"required":["survey_series_id","survey_id","survey_title","questions_created","api_key_id"],"title":"SurveyCreatedData","type":"object"},"SurveyPublishedEvent":{"additionalProperties":false,"properties":{"event":{"const":"survey.published","title":"Event","type":"string"},"timestamp":{"description":"ISO-8601 UTC timestamp","title":"Timestamp","type":"string"},"data":{"$ref":"#/components/schemas/SurveyPublishedData"}},"required":["event","timestamp","data"],"title":"SurveyPublishedEvent","type":"object"},"SurveyPublishedData":{"properties":{"survey_series_id":{"title":"Survey Series Id","type":"integer"},"survey_id":{"title":"Survey Id","type":"integer"},"survey_name":{"title":"Survey Name","type":"string"},"api_key_id":{"title":"Api Key Id","type":"integer"}},"required":["survey_series_id","survey_id","survey_name","api_key_id"],"title":"SurveyPublishedData","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 and on 503 responses from the global rate limiter. See ``src/utils/middleware/rate_limiting.py``.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets.","schema":{"type":"integer","minimum":0}}}},"paths":{"/api/webhooks":{"post":{"summary":"Create a webhook","description":"Register an endpoint to receive real-time event notifications. The signing secret is returned once on creation — save it immediately, it cannot be retrieved again. Subscribable events: `content.pushed`, `survey.created`, `survey.published`, `series.created`.","tags":["Webhooks"],"operationId":"webhooks_post_create","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookRequest"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CreateWebhookResponse"}},"required":["data"]}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Forbidden — insufficient permission","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}},"callbacks":{"content.pushed":{"{$request.body#/url}":{"post":{"summary":"Receive a content.pushed event","description":"BoundaryAI will POST to your subscribed URL when the `content.pushed` event fires. The body is signed with the subscription's HMAC secret via the `X-Boundary-Signature: sha256=<hex>` header. Respond 2xx within 10s or the delivery is retried with exponential backoff.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentPushedEvent"}}}},"responses":{"200":{"description":"Acknowledged — no retry"},"204":{"description":"Acknowledged — no retry"},"4XX":{"description":"Receiver rejected the delivery — BoundaryAI retries with exponential backoff."},"5XX":{"description":"Receiver errored — BoundaryAI retries with exponential backoff."},"default":{"description":"Any non-2xx triggers retry with exponential backoff up to N attempts."}}}}},"series.created":{"{$request.body#/url}":{"post":{"summary":"Receive a series.created event","description":"BoundaryAI will POST to your subscribed URL when the `series.created` event fires. The body is signed with the subscription's HMAC secret via the `X-Boundary-Signature: sha256=<hex>` header. Respond 2xx within 10s or the delivery is retried with exponential backoff.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeriesCreatedEvent"}}}},"responses":{"200":{"description":"Acknowledged — no retry"},"204":{"description":"Acknowledged — no retry"},"4XX":{"description":"Receiver rejected the delivery — BoundaryAI retries with exponential backoff."},"5XX":{"description":"Receiver errored — BoundaryAI retries with exponential backoff."},"default":{"description":"Any non-2xx triggers retry with exponential backoff up to N attempts."}}}}},"survey.created":{"{$request.body#/url}":{"post":{"summary":"Receive a survey.created event","description":"BoundaryAI will POST to your subscribed URL when the `survey.created` event fires. The body is signed with the subscription's HMAC secret via the `X-Boundary-Signature: sha256=<hex>` header. Respond 2xx within 10s or the delivery is retried with exponential backoff.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyCreatedEvent"}}}},"responses":{"200":{"description":"Acknowledged — no retry"},"204":{"description":"Acknowledged — no retry"},"4XX":{"description":"Receiver rejected the delivery — BoundaryAI retries with exponential backoff."},"5XX":{"description":"Receiver errored — BoundaryAI retries with exponential backoff."},"default":{"description":"Any non-2xx triggers retry with exponential backoff up to N attempts."}}}}},"survey.published":{"{$request.body#/url}":{"post":{"summary":"Receive a survey.published event","description":"BoundaryAI will POST to your subscribed URL when the `survey.published` event fires. The body is signed with the subscription's HMAC secret via the `X-Boundary-Signature: sha256=<hex>` header. Respond 2xx within 10s or the delivery is retried with exponential backoff.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyPublishedEvent"}}}},"responses":{"200":{"description":"Acknowledged — no retry"},"204":{"description":"Acknowledged — no retry"},"4XX":{"description":"Receiver rejected the delivery — BoundaryAI retries with exponential backoff."},"5XX":{"description":"Receiver errored — BoundaryAI retries with exponential backoff."},"default":{"description":"Any non-2xx triggers retry with exponential backoff up to N attempts."}}}}}}}}}}
```

## Receive a signed webhook delivery

> Webhook deliveries are sent as HTTP POST requests to your endpoint, signed with HMAC-SHA256 in the \`X-Boundary-Signature\` header. The event type is in \`X-Boundary-Event\`; the body carries \`event\`, \`timestamp\`, and \`data\`.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Receive real-time notifications when events occur in BAI Analytics. Deliveries are signed with HMAC-SHA256 in the `X-Boundary-Signature` header; the event type is in `X-Boundary-Event`."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[{"webhookSignature":[]}],"components":{"securitySchemes":{"webhookSignature":{"type":"apiKey","in":"header","name":"svix-signature","description":"HMAC-SHA256 signature of the raw request body in the `X-Boundary-Signature` header, computed with the signing secret returned at webhook creation."}},"schemas":{"ClerkWebhookResult":{"additionalProperties":true,"description":"Body of POST /webhooks (after successful signature verification).\n\nThe 200 is intentional even on handler errors — see the route\ndocstring for the retry-storm rationale. The discriminating field is\n``status``, not the HTTP code.","properties":{"status":{"enum":["success","unhandled","error"],"title":"Status","type":"string"},"event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The Clerk event type (e.g. user.created, organization.updated)","title":"Event Type"},"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Clerk's event ID","title":"Event Id"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Handler error message — only present on status=error","title":"Error"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"title":"Message"}},"required":["status"],"title":"ClerkWebhookResult","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 and on 503 responses from the global rate limiter. See ``src/utils/middleware/rate_limiting.py``.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets.","schema":{"type":"integer","minimum":0}}}},"paths":{"/webhooks":{"post":{"summary":"Receive a signed webhook delivery","description":"Webhook deliveries are sent as HTTP POST requests to your endpoint, signed with HMAC-SHA256 in the `X-Boundary-Signature` header. The event type is in `X-Boundary-Event`; the body carries `event`, `timestamp`, and `data`.","tags":["Webhooks"],"operationId":"webhooks_post_clerk_event","parameters":[{"name":"svix-id","in":"header","required":true,"schema":{"type":"string"},"description":"Unique delivery ID from Svix"},{"name":"svix-timestamp","in":"header","required":true,"schema":{"type":"string"},"description":"Unix timestamp; replay-protected by Svix"},{"name":"svix-signature","in":"header","required":true,"schema":{"type":"string"},"description":"HMAC-SHA256 signature over body+timestamp"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClerkWebhookResult"}}}},"400":{"description":"Bad Request — validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Unauthorized — missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## List supported webhook events

> Returns the catalogue of event types you can subscribe to.

```json
{"openapi":"3.1.0","info":{"title":"BAI Analytics API","version":"1.0.0"},"tags":[{"name":"Webhooks","description":"Receive real-time notifications when events occur in BAI Analytics. Deliveries are signed with HMAC-SHA256 in the `X-Boundary-Signature` header; the event type is in `X-Boundary-Event`."}],"servers":[{"url":"https://app.boundary-ai.com","description":"Production"}],"security":[],"paths":{"/webhooks/supported-events":{"get":{"summary":"List supported webhook events","description":"Returns the catalogue of event types you can subscribe to.","tags":["Webhooks"],"operationId":"webhooks_get_supported_events","responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SupportedEventsResponse"}},"required":["data"]}}}},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"description":"Internal Server Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"components":{"schemas":{"SupportedEventsResponse":{"properties":{"supported_events":{"description":"List of Clerk event types this server knows how to handle","items":{"type":"string"},"title":"Supported Events","type":"array"},"total_count":{"minimum":0,"title":"Total Count","type":"integer"}},"required":["supported_events","total_count"],"title":"SupportedEventsResponse","type":"object"},"ErrorEnvelope":{"description":"Top-level error response.\n\nSchema name in the OpenAPI document: ``ErrorEnvelope``.","properties":{"error":{"$ref":"#/components/schemas/ErrorObject"},"details":{"anyOf":[{},{"type":"null"}],"default":null,"description":"Optional structured details (e.g. validation field map)","title":"Details"}},"required":["error"],"title":"ErrorEnvelope","type":"object"},"ErrorObject":{"description":"The inner ``error`` field of an error envelope.\n\nSchema name in the OpenAPI document: ``ErrorObject``.","properties":{"code":{"description":"Machine-readable error code drawn from ErrorCode enum","title":"Code","type":"string"},"message":{"description":"Human-readable error message, safe to display","title":"Message","type":"string"}},"required":["code","message"],"title":"ErrorObject","type":"object"}},"responses":{"RateLimited":{"description":"Rate limit exceeded — see Retry-After header.","headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/XRateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/XRateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/XRateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"headers":{"RetryAfter":{"description":"Seconds to wait before retrying. Emitted on 429 and on 503 responses from the global rate limiter. See ``src/utils/middleware/rate_limiting.py``.","schema":{"type":"integer","minimum":1}},"XRateLimitLimit":{"description":"Total requests allowed in the current rate-limit window.","schema":{"type":"integer","minimum":1}},"XRateLimitRemaining":{"description":"Requests remaining in the current rate-limit window.","schema":{"type":"integer","minimum":0}},"XRateLimitReset":{"description":"Unix timestamp when the rate-limit window resets.","schema":{"type":"integer","minimum":0}}}}}
```
