Skip to main contentSkip to Content
Webhooks

Webhooks

CampaignOS supports both outbound and inbound webhooks for integrating with external systems.

  • Outbound webhooks send data from CampaignOS to your systems when events occur.
  • Inbound webhooks receive data from your systems into CampaignOS.

Outbound Webhooks

How It Works

  1. Create a webhook endpoint with the events you want to subscribe to.
  2. CampaignOS sends a signed POST request to your URL whenever a matching event occurs.
  3. Your server verifies the signature and processes the payload.

Payload Format

All outbound webhook deliveries follow this format:

{ "event": "contact.created", "timestamp": "2026-01-15T10:00:00.000Z", "data": { "contactId": "ct_abc123", "email": "jane@example.com", "firstName": "Jane", "lastName": "Doe" } }

Headers

HeaderDescription
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 hex digest of "<timestamp>.<body>"
X-Webhook-TimestampUnix timestamp (seconds) used in the signature; reject requests older than 5 minutes
X-Webhook-EventThe event type (e.g. contact.created)
X-Webhook-DeliveryUnique delivery ID
X-Webhook-AttemptDelivery attempt number (retries use backoff of 2s and 8s)

Signature Verification

Verify the X-Webhook-Signature header using your endpoint’s signing secret. The signature covers the timestamp and the raw body joined by a dot:

const crypto = require("crypto"); function verifyWebhook(rawBody, headers, secret) { const timestamp = headers["x-webhook-timestamp"]; const signature = headers["x-webhook-signature"] ?? ""; if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const expected = crypto .createHmac("sha256", secret) .update(`${timestamp}.${rawBody}`) .digest("hex"); return ( signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) ); }

Inbound Webhooks

How It Works

  1. Create an inbound webhook endpoint in CampaignOS.
  2. Copy the generated URL and signing secret.
  3. Send data from your external systems to this URL.
  4. CampaignOS processes the payload, creates events, and optionally auto-creates contacts.

Endpoint URL

POST /api/webhooks/inbound/{endpointId}

Payload Format

{ "type": "user.signup", "email": "jane@example.com", "firstName": "Jane", "lastName": "Doe", "phone": "+1234567890", "metadata": { "source": "external-crm", "plan": "pro" } }
FieldTypeRequiredDescription
typestringYesEvent type to record (e.g. user.signup, purchase.completed)
emailstringNoContact email — used to find or auto-create a contact
contactIdstringNoCampaignOS contact ID (if known)
firstNamestringNoContact first name (used when auto-creating)
lastNamestringNoContact last name (used when auto-creating)
phonestringNoContact phone (used when auto-creating)
metadataobjectNoAdditional data stored with the event

Auto-Create Contacts

When you include an email in the payload and no matching contact exists in your workspace, CampaignOS automatically creates a new contact with the provided details (email, firstName, lastName, phone). This means you can send events from external systems without pre-creating contacts.

Signing Inbound Requests

If you configure a signing secret, include a signature header:

X-Webhook-Signature: <hmac_hex_digest>

Computed as HMAC-SHA256 of the raw request body using your signing secret.

cURL Example

curl -X POST https://your-domain.com/api/webhooks/inbound/ENDPOINT_ID \ -H "Content-Type: application/json" \ -H "X-Webhook-Signature: YOUR_SIGNATURE" \ -d '{ "type": "user.signup", "email": "jane@example.com", "firstName": "Jane", "metadata": { "source": "crm", "plan": "pro" } }'

Response

{ "received": true, "deliveryId": "del_abc123" }

API Reference

List Webhook Endpoints

GET /api/workspaces/{workspaceId}/webhooks

Lists all configured webhook endpoints with delivery counts.

Auth: Session required

Response:

[ { "id": "wh_abc123", "name": "CRM Sync", "url": "https://hooks.example.com/campaignos", "events": ["contact.created", "message.sent"], "direction": "OUTBOUND", "active": true, "_count": { "deliveries": 1500 }, "createdAt": "2026-01-15T10:00:00.000Z" } ]

Create Webhook Endpoint

POST /api/workspaces/{workspaceId}/webhooks

Auth: Session + ADMIN role required

Request Body:

{ "name": "CRM Sync", "url": "https://hooks.example.com/campaignos", "events": ["contact.created", "contact.updated", "message.sent"], "direction": "OUTBOUND" }
FieldTypeRequiredDescription
namestringNoHuman-readable label (max 100 chars)
urlstringOutbound onlyWebhook destination URL
eventsstring[]NoEvent types to subscribe to
directionstringNoINBOUND or OUTBOUND (default: OUTBOUND)

Response (201): Webhook endpoint with secret (shown only once).

Update Webhook Endpoint

PATCH /api/workspaces/{workspaceId}/webhooks/{webhookId}

Auth: Session + ADMIN role required

Request Body:

{ "name": "Updated Name", "url": "https://new-url.example.com/hook", "events": ["contact.created"], "active": false }

Delete Webhook Endpoint

DELETE /api/workspaces/{workspaceId}/webhooks/{webhookId}

Auth: Session + ADMIN role required. Cascades to all delivery records.

List Deliveries

GET /api/workspaces/{workspaceId}/webhooks/deliveries

Lists webhook delivery logs with pagination and filtering.

Query Parameters:

ParamTypeDescription
pagenumberPage number (default: 1)
limitnumberItems per page (default: 50, max: 100)
endpointIdstringFilter by endpoint
successbooleanFilter by success status
eventstringFilter by event type

Retry Delivery

POST /api/workspaces/{workspaceId}/webhooks/deliveries/{deliveryId}/retry

Auth: Session + ADMIN role required. Only outbound deliveries can be retried.


Webhook Events

These are the event types currently emitted by CampaignOS.

Contact Events

EventDescriptionPayload Fields
contact.createdNew contact createdcontactId, email, firstName, lastName
contact.updatedContact profile updatedcontactId, email, firstName, lastName
contact.deletedContact permanently removedcontactId, email

Campaign Events

EventDescriptionPayload Fields
campaign.contact.enrolledContact enters a campaigncampaignId, contactId

Message Events

EventDescriptionPayload Fields
message.sentMessage dispatched to a channelcontactId, channel, messageId, status, campaignId

Subscription Events

EventDescriptionPayload Fields
unsubscribe.emailContact opts out of emailcontactId, email, externalId, channel, source, messageId (when from a link)
unsubscribe.pushContact opts out of pushcontactId, email, externalId, channel, source
unsubscribe.telegramContact opts out of TelegramcontactId, email, externalId, channel, source
unsubscribe.allGlobal unsubscribe from all channelscontactId, email, externalId, channel, source

An opt-out fires these whatever its origin: an unsubscribe link, the preference centre, the ingest endpoint’s emailOptOut, the consent API or an automation step. source says which. Use them to keep other systems (another mailing tool, a CRM) in step.

Form Events

EventDescriptionPayload Fields
form.submittedForm submission receivedformId, submissionId, fields
Last updated on