Skip to main content
Fires whenever the status of a Campaign changes. The payload includes both the previous and new status, so consumers can react to specific transitions (e.g., notify on finished, archive on inactive). When new_status is finished, the payload also includes the campaign’s analytics snapshot, so you don’t need an extra API call to summarize a finished campaign.
  • Trigger: Any campaign status transition (pending to ready, active to inactive, active to cooldown, cooldown to finished, etc.).
  • Filtering: When configuring the webhook in Altur, you can restrict deliveries to a list of target statuses via the filters.status array.

Request

  • Method: POST
  • Content-Type: application/json
  • Endpoint: The URL you configure in your webhook integration.
  • Authentication: X-Altur-Signature header for HMAC verification. See Webhooks Overview.

Payload Example

{
  "event_id": "evt_3kQpZ7tA9bN2cV1hX0sR",
  "event_type": "campaign.status_changed",
  "occurred_at": "2026-06-08T14:23:15.482000-06:00",
  "api_version": "1.0",
  "project_id": "prj_8YqL3mZxR1tV0nKfH9bA",
  "data": {
    "campaign": {
      "id": 1234,
      "name": "Q2 Reactivation",
      "status": "finished",
      "previous_status": "cooldown"
    },
    "analytics": {
      "calls": 1820,
      "callsAnsweredByHuman": 945,
      "callsAnsweredByMachine": 612,
      "callsAnsweredByUnknown": 263,
      "callsAnsweredByHumanRate": 0.519,
      "callsAnsweredByMachineRate": 0.336,
      "callsAnsweredByUnknownRate": 0.144,
      "contacts": 2000,
      "contactsProcessed": 1820,
      "contactsProcessedRate": 0.91,
      "contactsFailed": 80,
      "contactsFailedRate": 0.04,
      "contactsVoicemail": 612,
      "contactsVoicemailRate": 0.306,
      "contactsAnswered": 945,
      "contactsAnsweredRate": 0.4725,
      "contactsConverted": 312,
      "contactsConvertedRate": 0.156
    }
  }
}

Envelope Fields

FieldTypeDescription
event_idstringUnique identifier for this delivery. Use for idempotent processing.
event_typestringAlways campaign.status_changed.
occurred_atstringISO 8601 timestamp in the project’s timezone.
api_versionstringWebhook payload schema version (currently 1.0).
project_idstringPublic identifier of the project that owns the campaign.
dataobjectEvent payload (see below).

data.campaign Object

FieldTypeDescription
idintCampaign identifier.
namestringCampaign name.
statusstringThe new status (after the transition).
previous_statusstringThe status the campaign was in before the transition.

data.analytics Object (only when status = finished)

For phone-call campaigns, the snapshot contains call and contact counts and rates. For WhatsApp campaigns it contains message lifecycle counts and rates. See the Retrieve Campaign reference for the full shape. The analytics block here matches the analytics field on the campaign detail response.

Response

Return 200 OK to confirm receipt. Failures are retried per the delivery retry policy.

Status Filtering

When configuring the webhook integration, pass filters.status as an array of target statuses (e.g., ["finished"]). Only transitions whose new_status is in that list will be delivered. Omitting the filter delivers every status transition.

Receiver Examples

For signature verification, see is_valid_signature in the Webhooks Overview.
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.post("/webhooks/altur/campaign-status")
def on_campaign_status():
    signature = request.headers.get("X-Altur-Signature")
    if not is_valid_signature(SHARED_SECRET, request.get_json(), signature):
        return jsonify(error="invalid signature"), 401

    event = request.get_json()
    data = event["data"]
    campaign = data["campaign"]

    if campaign["status"] == "finished":
        analytics = data.get("analytics") or {}
        archive_campaign_in_crm(campaign["id"], analytics)
    elif campaign["status"] == "inactive":
        pause_downstream_billing(campaign["id"])

    return "", 200