Events
2 endpoints
/v1/eventsPush a custom event that triggers automations
Generic inbound webhook per account: an external source (Zapier, Make,
n8n, a client backend, a third-party tool) pushes a named event with
its data. Capturia resolves the contact (external_id → phone →
email), enriches the contact non-destructively, fills in the mapped custom
fields, records the event on the contact, then starts
the automations whose "Custom event" trigger
listens for this event name — with the payload available in
messages through {{event.<key>}}.
Contact resolution
At least one identifier is required (external_id, email or phone).
If no contact matches, a minimal contact is created — unless
contact.create_if_missing is false, in which case the request returns
404 contact_not_found without recording anything. If the identifiers
only match an archived contact, the event is recorded on it
(for traceability) but the contact is not modified and no automation
is enrolled (contact_archived: true).
Consent (Law 25 / CASL)
Receiving an event does not establish any communication consent. A contact created through this path cannot receive SMS/email until consent is captured through another flow; a triggered automation that tries to send without consent is blocked by the execution engine's guards.
Optional HMAC signature
In addition to the API key, the request can carry a signature
X-Capturia-Signature: t=<unix>,v1=<hex> — an HMAC-SHA256 of
${t}.${raw body} computed with the raw API key as the secret,
with a 5-minute anti-replay window. As soon as the header is present,
validation is strict (fail-closed): an invalid signature = 401 invalid_signature. Without the header, the API key alone authenticates the request.
Idempotency
The Idempotency-Key header is optional but recommended for
retries: a second request with the same key and the same payload returns the
original response without ingesting the event again (stored for 24h; a
different payload = 409 idempotency_conflict). In addition, automation
enrollment has its own 5-minute deduplication per contact and per
automation.
Custom error codes
- 429 —
rate_limited_ipwhen the IP rate limit (120 req/min) is reached, with headerX-RateLimit-Scope: ip. Per-key limit: 60 req/min (adjustable per key).
events:ingestRequest body
| Property | Type | Required | Description |
|---|---|---|---|
event | string | required | Event name, in the account's namespace. Compared as-is (case-sensitive) to the name configured on the automations' "Custom event" trigger. |
contact | EventIngestContact | required | — |
payload | object | optional | Arbitrary event data. Each top-level key is
available to automation messages through {{event.<key>}} (a
nested object is serialized as JSON). The stored content is capped
(number of keys, depth, total size). |
custom_fields | object | optional | Custom fields of the contact, keyed by the definition's name
(same contract as /v1/leads/capture). A key with no matching
definition is silently ignored. Additive merge: the
new value wins on collision. |
source_label | string | optional | Free-form label identifying the source (shown on the contact). |
Responses
| Status | Description |
|---|---|
200 | Event ingested ( |
400 | The request is malformed (missing header, invalid JSON, invalid parameter). |
401 | Unknown API key or invalid format. |
403 | Authorization denied. Two causes, distinguished by |
404 | The requested resource doesn't exist (or isn't within the tenant's scope). |
409 | A previous request with the same |
422 | Parameter or body validation failed. |
429 | The per-window request quota has been exceeded for this API key or endpoint. |
500 | Unexpected server error. Always include the |
Example request
curl -X POST 'https://api.capturia.io/v1/events' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"event": "abandon_panier",
"contact": {
"external_id": "user-4821",
"external_source": "mon-backend",
"email": "[email protected]",
"first_name": "Alex"
},
"payload": {
"montant": 249.99,
"produit": "Forfait Pro",
"url_panier": "https://boutique.example.com/panier/abc123"
},
"custom_fields": {
"plan_vise": "pro"
},
"source_label": "Boutique en ligne"
}'/v1/event-registrationsRegister a contact for an event
Registers an existing contact of the account for a published event (webinar, challenge, summit, online event). The person then receives the event's messages, just as if they had registered through its page.
One registration per person and per edition: a second call
for the same contact and the same edition returns the same registration with
200 and created: false, without resending the confirmation. It updates the
language, and re-registers a person who had said they would not
attend; a person who asked to stop receiving messages stays
opted out. The call is therefore safe to retry, without an
Idempotency-Key header.
Requires the plan's Event support module
(403 feature_not_available otherwise) and the contacts:write scope.
Rejections
404 resource_not_foundwithdetails.resource:contact(contact missing, merged or from another account),eventoredition.422 business_rule_violationwithdetails.rule:event_not_published(draft),event_closed(event archived, edition cancelled, or no upcoming edition),event_edition_ended(edition ended).400 validation_error: invalid body, orevent_idandevent_slugboth present or both missing.
contacts:writeRequest body
| Property | Type | Required | Description |
|---|---|---|---|
contact_id | string | required | Contact to register. |
event_id | string | optional | Identifier of the event. |
event_slug | string | optional | Address of the event, as it appears in its registration link. |
edition_id | string | optional | Target edition. If omitted: the event's next open edition, as on its registration page. |
language | string | optional | Language of the messages. If omitted: the event's first language. |
Responses
| Status | Description |
|---|---|
200 | The contact was already registered for this edition; the same registration is returned. |
201 | Registration created. |
400 | Parameter or body validation failed. |
401 | Unknown API key or invalid format. |
403 | Authorization denied. Two causes, distinguished by |
404 | The requested resource doesn't exist (or isn't within the tenant's scope). |
415 | The |
422 | The request is syntactically valid but violates a business rule. |
429 | The per-window request quota has been exceeded for this API key or endpoint. |
500 | Unexpected server error. Always include the |
503 | Service temporarily unavailable (maintenance, external dependency down, degraded infrastructure). Clients should retry with exponential backoff. |
Example request
curl -X POST 'https://api.capturia.io/v1/event-registrations' \
-H 'Authorization: Bearer cap_live_<your-key>' \
-H 'Content-Type: application/json' \
-d '{
"contact_id": "9b8a7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"event_slug": "defi-5-jours",
"language": "fr"
}'