Skip to main content

Outgoing Webhooks

Outgoing webhooks allow you to receive real-time HTTP notifications when events occur in your workspace. When an event fires, CampaignLark sends a POST request to your configured endpoint with a JSON payload describing the event.

Use Cases

Outgoing webhooks are ideal for:

  • Syncing contact data to external CRMs
  • Triggering workflows in automation platforms
  • Logging engagement events to analytics systems
  • Building custom integrations and workflows

Webhook Delivery

Request Format

All webhook deliveries are sent as POST requests with the following headers:

HeaderValue
Content-Typeapplication/json
X-Webhook-SignatureHMAC-SHA256 hex digest of the request body
User-AgentCampaignLark/1.0 (+https://campaignlark.com)

Envelope

Every webhook delivery follows this envelope structure:

{
"id": "evt_69b27e24be03b743d4b9b7b7",
"event_type": "contact.created",
"workspace_id": 12,
"created_at": "2026-03-12T08:49:40.208Z",
"data": { ... }
}
FieldTypeDescription
idstringUnique event ID, prefixed with evt_
event_typestringThe event type that triggered this delivery
workspace_idintegerThe workspace where the event occurred
created_atstringISO 8601 timestamp of when the event was created
dataobjectEvent-specific payload (see below)

Verifying Signatures

Security Required

Every webhook includes an X-Webhook-Signature header containing an HMAC-SHA256 hex digest of the raw request body, signed with your webhook secret. Always verify this signature before processing the payload to ensure the request came from CampaignLark.

const crypto = require("crypto");

function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");

return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}

Retry Policy

Your endpoint must respond with a 2xx status code to acknowledge receipt. If delivery fails (non-2xx response or network error), CampaignLark retries with the following backoff schedule:

AttemptDelay
1Immediate
25 seconds
31 minute
45 minutes
510 minutes
630 minutes
71 hour
84 hours

After 8 failed attempts, the delivery is permanently discarded. Your endpoint must respond within 10 seconds or the delivery will be treated as a failure.

Total Retry Window

The complete retry cycle spans approximately 5.5 hours from the initial attempt to the final retry.


The Contact Object

Many events include a contact object. This object always has the same shape, regardless of the event type:

{
"id": "69b27e24be03b743d4b9b7b3",
"status": "SUBSCRIBED",
"fields": {
"email_address": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"phone_number": null,
"country": null
},
"tags": [
{ "id": 1, "name": "VIP" },
{ "id": 5, "name": "Newsletter" }
],
"created_at": "2026-03-12T08:34:16Z",
"updated_at": "2026-03-12T08:49:40Z"
}
FieldTypeDescription
idstringThe contact's unique ID
statusstringOne of SUBSCRIBED, UNCONFIRMED, UNSUBSCRIBED, CLEANED, COMPLAINED
fieldsobjectMap of merge tag to value. Unset fields are null.
tagsarrayArray of { id, name } objects for each tag assigned to the contact
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp
last_opened_atstring?ISO 8601 timestamp, present only if the contact has opened an email
last_clicked_atstring?ISO 8601 timestamp, present only if the contact has clicked a link
unsubscribed_atstring?ISO 8601 timestamp, present only if the contact has unsubscribed

Event Types

Contact Lifecycle

contact.created

Fired when a new contact is created.

Payload: Contact Object

{
"id": "69b27e24be03b743d4b9b7b3",
"status": "SUBSCRIBED",
"fields": { "email_address": "jane@example.com", "first_name": "Jane" },
"tags": [],
"created_at": "2026-03-12T08:34:16Z",
"updated_at": "2026-03-12T08:34:16Z"
}

contact.updated

Fired when a contact's fields, status, or tags are modified.

Payload: Contact Object

{
"id": "69b27e24be03b743d4b9b7b3",
"status": "SUBSCRIBED",
"fields": { "email_address": "jane@example.com", "first_name": "Jane", "last_name": "Doe" },
"tags": [{ "id": 1, "name": "VIP" }],
"created_at": "2026-03-12T08:34:16Z",
"updated_at": "2026-03-12T09:15:00Z"
}

contact.deleted

Fired when a contact is deleted.

Payload: Contact Object (snapshot at time of deletion)

{
"id": "69b27e24be03b743d4b9b7b3",
"status": "SUBSCRIBED",
"fields": { "email_address": "jane@example.com", "first_name": "Jane" },
"tags": [{ "id": 1, "name": "VIP" }],
"created_at": "2026-03-12T08:34:16Z",
"updated_at": "2026-03-12T08:34:16Z"
}

contact.subscribed

Fired when a contact's status changes to SUBSCRIBED (e.g. via opt-in confirmation or resubscription).

Payload: Same as contact.created

contact.unsubscribed

Fired when a contact unsubscribes.

Payload: Same as contact.created

contact.cleaned

Fired when a contact is marked as cleaned due to a hard bounce.

Payload:

{
"contact": { ... },
"message_id": "abc123@mail.example.com",
"bounce_reason": "550 5.1.1 User unknown"
}
FieldTypeDescription
contactobjectContact Object
message_idstringThe message ID of the bounced email
bounce_reasonstringThe bounce response message from the receiving server

contact.complained

Fired when a contact reports an email as spam (feedback loop).

Payload:

{
"contact": { ... },
"message_id": "abc123@mail.example.com",
"campaign_id": "69b27e24be03b743d4b9b7b3"
}
FieldTypeDescription
contactobjectContact Object
message_idstringThe message ID of the complained email
campaign_idstring?Campaign ID, if the email was sent from a campaign
automation_idstring?Automation ID, if the email was sent from an automation

Tags

contact.tagged

Fired when one or more tags are added to a contact.

Payload:

{
"contact": { ... },
"modified_tags": [
{ "id": 1, "name": "VIP" },
{ "id": 5, "name": "Newsletter" }
]
}
FieldTypeDescription
contactobjectContact Object
modified_tagsarrayThe tags that were added, each with id and name

contact.untagged

Fired when one or more tags are removed from a contact.

Payload: Same as contact.tagged


Segments

contact.segment.entered

Fired when a contact enters a segment after a recalculation.

Payload:

{
"contact": { "id": "69b27e24be03b743d4b9b7b3" },
"segment": { "id": 4, "name": "Engaged Users" }
}
FieldTypeDescription
contactobjectContains only the contact id
segmentobjectThe segment entered, with id and name
note

Segment events provide a lightweight contact reference (id only) instead of the full contact object for performance reasons, as segment recalculations can affect thousands of contacts at once.

contact.segment.exited

Fired when a contact exits a segment after a recalculation.

Payload: Same as contact.segment.entered


Email Engagement

email.sent

Fired when an email is successfully delivered to the recipient's mail server.

Payload:

{
"contact_id": "69b27e24be03b743d4b9b7b3",
"message_id": "abc123@mail.example.com",
"campaign_id": "69b27e24be03b743d4b9b7b3"
}
FieldTypeDescription
contact_idstringThe recipient contact ID
message_idstringThe unique message ID assigned by the mail server
campaign_idstring?Present if the email was part of a campaign
automation_idstring?Present if the email was part of an automation

email.opened

Fired when a contact opens an email.

Payload: Same structure as email.sent.

email.clicked

Fired when a contact clicks a link in an email.

Payload:

{
"contact_id": "69b27e24be03b743d4b9b7b3",
"message_id": "abc123@mail.example.com",
"campaign_id": "69b27e24be03b743d4b9b7b3",
"url": "https://example.com/offer"
}
FieldTypeDescription
contact_idstringThe recipient contact ID
message_idstringThe unique message ID
campaign_idstring?Present if the email was part of a campaign
automation_idstring?Present if the email was part of an automation
urlstringThe URL that was clicked

email.bounced

Fired when an email bounces.

Payload:

{
"contact_id": "69b27e24be03b743d4b9b7b3",
"message_id": "abc123@mail.example.com",
"campaign_id": "69b27e24be03b743d4b9b7b3",
"bounce_type": "hard",
"bounce_reason": "550 5.1.1 User unknown"
}
FieldTypeDescription
contact_idstringThe recipient contact ID
message_idstringThe unique message ID
campaign_idstring?Present if the email was part of a campaign
automation_idstring?Present if the email was part of an automation
bounce_typestringThe bounce category (e.g. hard, soft)
bounce_reasonstringThe response message from the receiving server

Forms

form.submitted

Fired when a contact submits a form.

Payload:

{
"form": {
"id": 1,
"name": "Newsletter Signup"
},
"contact": { ... },
"ip_address": "1.1.1.1",
"country": "AU",
"asn": "AS7545",
"is_proxy": false
}
FieldTypeDescription
formobjectThe form that was submitted, with id and name
contactobjectContact Object of the created or updated contact
ip_addressstringThe IP address of the visitor
countrystringTwo-letter country code based on IP geolocation
asnstringAutonomous System Number of the visitor's network
is_proxybooleanWhether the visitor is using a VPN or proxy

Best Practices

Implementation Guidelines

Security:

  • Always verify the X-Webhook-Signature header before processing any payload
  • Use constant-time comparison functions to prevent timing attacks
  • Keep your webhook secret secure

Performance:

  • Respond with a 200 status code immediately upon receipt
  • Process webhook data asynchronously if your logic takes more than a few seconds
  • Your endpoint must respond within 10 seconds to avoid timeouts

Reliability:

  • Handle duplicates — in rare cases, the same event may be delivered more than once. Use the id field to deduplicate
  • Implement idempotent processing to safely handle duplicate deliveries
  • Monitor failures — if your endpoint consistently fails, deliveries will be discarded after 8 attempts
  • Use Webhook.site for debugging and inspecting webhook payloads during development
  • A single action may trigger multiple events (e.g. updating a contact and adding a tag manually fires both contact.updated and contact.tagged)