Campaign webhook subscriptions
Subscriptions send campaign events from Alvio to your CRM or system. Choose an HTTPS destination and the events it should receive. To send contacts in the other direction, see the webhook integration guide.
A destination stores the URL, authentication and delivery settings. You can reuse it across campaigns attached to the same business phone number. A subscription connects that destination to an event in one campaign. Creating a destination alone does not enable notifications.
Configure a subscription
- Open your campaign, then the Webhooks tab and Setup view.
- Enter a Destination name and Destination URL, for example
https://crm.example.com/webhooks/alvio. - Select the authentication your server expects: Bearer token, API key header, or No authentication. For an API key, enter the header name and value; the suggested name is
X-API-Key. - Optionally enter an Optional signing secret to sign requests. Select a Retry policy: no retries, one retry or up to three retries. Three retries are selected by default.
- Click Create endpoint, then select the destination under Available endpoints. You can also select a destination already created for this number.
- Choose events from Lead updates (
event_type: "update"), inspect their JSON previews and click Attach to campaign.LEAD_RECEIVEDandMEETING_BOOKEDare preselected; adjust them to your integration. - Test reception using the procedure below, then use History to monitor deliveries.
Destinations created from this screen use POST requests with a JSON body. Your destination's authentication credential is independent of the campaign x-api-key token used to send contacts into Alvio.
Existing subscriptions can be enabled, disabled or deleted. Disabling retains history, but pending deliveries processed while disabled are marked as failed; enabling the subscription again does not automatically resend them. Deleting a subscription also deletes its delivery history. Prefer disabling it when you need to retain history.
Available events
The following events use event_type: "update". A subscription does not trigger the business action itself: it receives a notification when the campaign produces the event.
event_name | Trigger | Useful data fields |
|---|---|---|
NO_WHATSAPP | The contact is identified as unreachable on WhatsApp. | reason, recommended_action, error |
CONTACT_STOP_REQUESTED | A confirmed request to stop calls or all contact is recorded. | scope, requested_at, evidence, source_message_id, restriction_applied |
MEETING_BOOKED | An appointment or callback is confirmed. | meeting_type, booking_status, start, end, timezone, assigned_to, location, notes, urgent, urgency_reason |
LEAD_RECEIVED | A lead is accepted by the campaign intake webhook. | contact_status, sent_immediately, skipped_immediate_send, is_existing_contact, existing_conversation_reused, error_message, received_via |
MESSAGE_SENT | A campaign message is sent, at any position in the sequence. | channel, step_order, step_name, message_position |
REPLY_DETECTED | A reply triggers the campaign event that stops follow-ups. | pause_reason, step_order, step_name |
ESCALATION | The conversation is escalated for human follow-up. | reason, category, priority, assigned_to, triggered_by, escalation_event_type, urgent, urgency_reason |
SEND_FAILED | A campaign send attempt fails. | reason, step_order, step_name |
MESSAGE_SENT does not confirm delivery or reading by the recipient. REPLY_DETECTED is not a feed of every incoming message. For delivered and read statuses, use contact result tracking. NO_WHATSAPP does not cover every possible send failure.
Integration-specific events
| Event | Type | Availability |
|---|---|---|
MEETING_RESCHEDULED | update | Rescheduling confirmed in the calendar, for integrations configured to handle rescheduling. |
MEETING_CANCELLED | update | Cancellation confirmed in the calendar, for integrations configured to handle cancellations. |
AVAILABILITY_REQUESTED | availability_request | Availability request for integrations configured for this flow; absent from the standard Lead updates selector. |
Do not assume a campaign produces these events simply because their names exist. Confirm their availability and contract with Alvio before integrating. Rescheduling/cancellation events can include operation_id, previous_outcome_id, outcome_id, calendar_event_id, previous_start, previous_end, start, end and timezone.
Common JSON envelope
| Field | Contents |
|---|---|
event_id | Event identifier. Preserved across retries and redeliveries; use it for deduplication. |
event_type | Event family, usually update. |
event_name | Event name from the tables above. |
version | Contract version: 2026-04-08. This is not the delivery date. |
occurred_at | Event timestamp in ISO 8601 format. |
lead | first_name, last_name, phone, email. Unavailable values can be null. |
external_references | CRM identifiers: contact_id, deal_id, account_id, or null. |
alvio | campaign_id, campaign_name, conversation_id, conversation_url, business_phone, has_whatsapp. Some fields can be null. |
data | Event-specific data, including urgency fields when available. |
source_data | Source data attached to the event, otherwise the contact's custom fields. Can be {}. |
CRM references are resolved from contact fields in this order: crm_contact_id, crmid, contact_id for the contact; crm_deal_id, dealid, deal_id for the deal; account_id, crm_account_id for the account. Supply stable identifiers when sending the lead.
has_whatsapp can be null when unknown; do not interpret this as false. source_data can contain personal data: avoid logging it in full unnecessarily. The standard contract does not include a full conversation transcript.
Confirmed appointment
Complete fictional example: booking recorded at 10:00 in Paris for 14:00 that day, without urgency.
{
"event_id": "11111111-1111-4111-8111-111111111001",
"event_type": "update",
"event_name": "MEETING_BOOKED",
"version": "2026-04-08",
"occurred_at": "2026-10-27T09:00:00Z",
"lead": {
"first_name": "Jane",
"last_name": "Doe",
"phone": "+33612345678",
"email": "jane@example.com"
},
"external_references": {
"contact_id": "CRM_123456",
"deal_id": "DEAL_789",
"account_id": null
},
"alvio": {
"campaign_id": "22222222-2222-4222-8222-222222222222",
"campaign_name": "Lead follow-up",
"conversation_id": "33333333-3333-4333-8333-333333333333",
"conversation_url": "https://alvio.ai/inbox/conversation/33333333-3333-4333-8333-333333333333",
"business_phone": "+33100000000",
"has_whatsapp": true
},
"data": {
"meeting_type": "appointment",
"booking_status": "confirmed",
"start": "2026-10-27T14:00:00+01:00",
"end": "2026-10-27T14:30:00+01:00",
"timezone": "Europe/Paris",
"assigned_to": null,
"location": null,
"notes": null,
"urgent": false,
"urgency_reason": null
},
"source_data": {
"crm_contact_id": "CRM_123456",
"crm_deal_id": "DEAL_789",
"source": "website"
}
}Urgent escalation
Complete fictional example: an explicit immediate callback request, with no confirmed calendar appointment.
{
"event_id": "11111111-1111-4111-8111-111111111002",
"event_type": "update",
"event_name": "ESCALATION",
"version": "2026-04-08",
"occurred_at": "2026-10-27T09:00:00Z",
"lead": {
"first_name": "Jane",
"last_name": "Doe",
"phone": "+33612345678",
"email": "jane@example.com"
},
"external_references": {
"contact_id": "CRM_123456",
"deal_id": "DEAL_789",
"account_id": null
},
"alvio": {
"campaign_id": "22222222-2222-4222-8222-222222222222",
"campaign_name": "Lead follow-up",
"conversation_id": "33333333-3333-4333-8333-333333333333",
"conversation_url": "https://alvio.ai/inbox/conversation/33333333-3333-4333-8333-333333333333",
"business_phone": "+33100000000",
"has_whatsapp": true
},
"data": {
"status": "escalated",
"reason": "The prospect requests an immediate callback.",
"category": "callback_request",
"priority": null,
"assigned_to": null,
"triggered_by": null,
"escalation_event_type": null,
"urgent": true,
"urgency_reason": "immediate_request"
},
"source_data": {
"crm_contact_id": "CRM_123456",
"crm_deal_id": "DEAL_789",
"source": "website"
}
}Use data.urgent to prioritize a callback. A high priority alone is not sufficient. Calculation rules, possible values and older-event behavior are detailed in Urgent appointments and callbacks. Urgency does not guarantee instant delivery.
STOP request
Complete fictional example of a request to stop all contact:
{
"event_id": "11111111-1111-4111-8111-111111111003",
"event_type": "update",
"event_name": "CONTACT_STOP_REQUESTED",
"version": "2026-04-08",
"occurred_at": "2026-10-27T09:00:00Z",
"lead": {
"first_name": "Jane",
"last_name": "Doe",
"phone": "+33612345678",
"email": "jane@example.com"
},
"external_references": {
"contact_id": "CRM_123456",
"deal_id": "DEAL_789",
"account_id": null
},
"alvio": {
"campaign_id": "22222222-2222-4222-8222-222222222222",
"campaign_name": "Lead follow-up",
"conversation_id": "33333333-3333-4333-8333-333333333333",
"conversation_url": "https://alvio.ai/inbox/conversation/33333333-3333-4333-8333-333333333333",
"business_phone": "+33100000000",
"has_whatsapp": null
},
"data": {
"status": "contact_stop_requested",
"scope": "all_contact",
"requested_at": "2026-10-27T09:00:00Z",
"evidence": "STOP",
"source_message_id": "44444444-4444-4444-8444-444444444444",
"restriction_applied": true,
"urgent": false,
"urgency_reason": null
},
"source_data": {
"crm_contact_id": "CRM_123456",
"crm_deal_id": "DEAL_789",
"source": "website"
}
}scope: "all_contact"means a request to stop all contact.scope: "phone_calls"means a request to stop calls; do not turn it into a restriction on every channel.requested_atandevidenceprovide the request timestamp and supporting evidence;source_message_ididentifies the original message.restriction_applied: truemeans the restriction was applied within Alvio. Your CRM still needs to apply the corresponding rule in its own tools.
A STOP request is distinct from an ordinary prospect reply. Its payload is frozen with the CRM references and request evidence. A subscription created after this event does not retroactively receive that STOP.
Authentication and signatures
Requests include Content-Type: application/json and the following headers:
| Header | Value |
|---|---|
X-Alvio-Event-Id | The JSON event_id |
X-Alvio-Event-Type | The JSON event_type |
X-Alvio-Event-Name | The JSON event_name |
Authorization | Bearer <your token> when Bearer token is selected |
| Custom API header | Your key when API key header is selected |
X-Alvio-Signature | Present when a signing secret is configured |
The signature format is sha256=<hexadecimal digest>. Compute HMAC-SHA256 over the UTF-8 bytes of the raw received JSON body, using the shared secret, and compare the digests in constant time. Verify before parsing or reformatting the JSON: changing whitespace or property order changes the signature. Reject a missing or invalid signature when your integration requires signing.
The signature does not include a separate delivery timestamp. Redelivery of the same event can be valid: combine verification with persistent deduplication by event_id. Use HTTPS and keep secrets on the server.
Delivery, retries and duplicates
- Acknowledge with HTTP 2xx after durably storing the event. Run slow processing asynchronously afterwards. A
deliveredstatus in Alvio means your endpoint responded successfully, not that your CRM completed its processing. - The timeout is 5 seconds by default for a destination created from the campaign screen.
- For standard HTTP delivery, network errors, timeouts and final HTTP responses outside 2xx trigger a retry when the policy allows it, including 4xx errors. Fix authentication or payload errors accordingly.
- With 3 retries, there are at most 4 attempts in the automatic cycle. Minimum delays after each failure are 1, 2 and then 4 minutes; these are not guaranteed delivery times.
- The queue is processed periodically, with a scheduled pass every minute. Load and ongoing processing can add delay. Do not assume strict ordering between events.
- The stored JSON and
event_idare preserved across retries and redeliveries. The same notification can therefore arrive more than once.
Atomically associate the event identifier with its processing in your receiver. If the event has already been durably accepted, respond 2xx without repeating the business action. In particular, avoid creating duplicate appointments or tasks for one event. Enabling a subscription is not a mechanism for exporting the entire past history.
Test reception
- Use a test campaign and test destination to avoid changing real CRM records.
- Select a destination and an event, then inspect the JSON preview. Previewing alone sends no request.
- Send a test. This makes a real HTTP request with the destination's authentication and signature. Check the displayed HTTP status, response and duration.
- If a matching subscription already exists, the test is saved in History with kind
test. Without a matching subscription, the result is displayed but not retained in that history. - Verify CRM references, deduplication and signature verification on the receiving side before using a production destination.
Tests are not all anonymized. Except for STOP, previews and tests can use the campaign's latest event or contact, with example values filling missing data. Their JSON does not consistently include a test: true flag; do not rely on that flag to isolate tests.
The STOP test uses an example without real phone, email, CRM or conversation identifiers, with test: true, restriction_applied: false and an event_id starting with sample_contact_stop_. It must not suppress a real contact.
History and manual redelivery
In History, filter by event, status or kind (live / test), or search for a payload value. The view loads the 30 most recent deliveries; filters apply to these entries rather than the entire history.
| Status | Meaning |
|---|---|
pending | Waiting for an initial attempt or retry. |
processing | An attempt is in progress. |
delivered | The receiver responded successfully. |
failed | Failed with no further automatic retry scheduled for this entry. |
Open a row to inspect its payload, response and errors. Manual redelivery uses the stored JSON and the same event_id, with the destination's current configuration, and adds a history entry linked to the original. It does not recalculate contact data or urgency. Tests and manual redeliveries are one-off sends without an automatic retry cycle. A redelivery does not remove any retry still pending on the original entry.
Troubleshooting
| Symptom | What to check |
|---|---|
| No events received | Check the campaign and business number, selected destination, active subscription and whether the event actually occurred. Creating a destination alone is not enough. |
Receiver returns 401 or 403 | Check the destination token or API header name and value. |
| Invalid signature | Check the shared secret and calculation over the raw body before any JSON transformation. |
Receiver returns 400 or 422 | Check the expected contract, event_name and support for null values. |
| Timeout or network error | Check URL accessibility and acknowledge quickly after durable storage. |
Subscription or endpoint inactive | The subscription or destination was inactive when processed. Enable the required configuration before a controlled redelivery. |
| Test missing from history | Check that a matching subscription existed at test time; also check filters and the 30 most recent entries. |
| Duplicate in CRM | Check deduplication by event_id, including manual redeliveries. |
Use this contract for a new integration. If you already receive a specific format with status, crm_id or conversation_transcript, retain your existing contract and confirm migration with Alvio; the names are not automatic aliases.