Skip to content

Join the Seedly owners community →

Help Center

Webhook Payloads

Reference for webhook event payload shapes delivered to your server.

Last updated

Reference for the exact payload delivered for each webhook event. All payloads follow the same envelope:

{
  "event": "event.name",
  "timestamp": "2026-03-27T14:30:00.000Z",
  "data": { ... }
}

Common fields in data:

  • subAccountId - Always present. Identifies which sub-account the event belongs to.
  • locationId - A stable public identifier for the sub-account, included alongside subAccountId. Prefer this when keying records in external systems.
  • contactId - Present when the event is associated with a specific contact.
  • tags - Present when the associated contact has tags. Array of tag strings.

Delivery headers and verification#

Every delivery carries these headers:

HeaderWhat it is
X-Webhook-Signaturesha256=<hex>. HMAC-SHA256 of the raw request body, keyed with your subscription's signing secret.
X-Webhook-Signature-V2sha256=<hex>. HMAC-SHA256 of <timestamp>.<raw body>, same secret. Optional, replay-protected.
X-Webhook-TimestampDelivery time in epoch milliseconds. This is the value signed by the V2 signature.
X-Webhook-EventThe event name, matching the event field in the body.
X-Webhook-DeliveryUnique id for this delivery attempt.
X-Webhook-Event-IdStable id for the underlying event. Use it to make your handler idempotent, since a retry repeats the same event id.
User-AgentCarries the sub-account's display brand, so the receiving system sees your brand rather than the platform name.

Which signature to verify#

Verify against the raw request body exactly as received, before any JSON parsing or re-serialization, or the bytes will not match.

X-Webhook-Signature (original). Signs the body alone. It proves the payload came from you and has not been altered. It cannot tell you when it was sent, so a captured request stays valid forever and can be replayed at any time.

X-Webhook-Signature-V2 (recommended for new integrations). Signs <timestamp>.<body>, following the same scheme Stripe uses. Because the timestamp is part of the signed string, your handler can trust X-Webhook-Timestamp and reject anything outside a freshness window you choose, which shuts the replay window. Verifying the original signature leaves that timestamp unsigned, so an attacker replaying a captured delivery can rewrite it freely.

Both headers are sent on every delivery, signed with the same secret. Nothing breaks if you keep verifying the original one; move to V2 when it suits you.

import crypto from "node:crypto";
 
// `rawBody` must be the exact bytes received, not a re-serialized object.
function verify(rawBody, headers, secret, toleranceMs = 5 * 60 * 1000) {
  const timestamp = headers["x-webhook-timestamp"];
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");
 
  const received = (headers["x-webhook-signature-v2"] || "").replace("sha256=", "");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(received, "hex");
 
  // Constant-time compare, then reject anything too old to be a live delivery.
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
  return Math.abs(Date.now() - Number(timestamp)) < toleranceMs;
}

Your signing secret is generated per subscription when you create the webhook, under Settings, Integrations, Webhooks.


contact.created#

Fires when a new contact is created.

{
  "event": "contact.created",
  "timestamp": "2026-03-27T14:30:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "[email protected]",
    "phone": "+15551234567",
    "company": "Acme Corp",
    "source": "api",
    "tags": ["website-lead"],
    "lifecycleStage": "lead"
  }
}

contact.updated#

Fires when a contact record is updated.

{
  "event": "contact.updated",
  "timestamp": "2026-03-27T14:31:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "[email protected]",
    "phone": "+15551234567",
    "company": "Acme Corp",
    "lifecycleStage": "qualified"
  }
}

contact.lifecycle_changed#

Fires when a contact's lifecycle stage changes.

{
  "event": "contact.lifecycle_changed",
  "timestamp": "2026-03-27T14:32:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "newStage": "customer",
    "previousStage": "qualified"
  }
}

contact.tag_added#

Fires when a tag is added to a contact.

{
  "event": "contact.tag_added",
  "timestamp": "2026-03-27T14:33:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "tagName": "vip"
  }
}

message.received#

Fires when an inbound message is received on any channel.

{
  "event": "message.received",
  "timestamp": "2026-03-27T14:34:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "messageId": "msg456",
    "conversationId": "conv789",
    "channel": "sms",
    "bodyText": "Hi, I'd like to schedule an appointment",
    "fromAddress": "+15559876543",
    "direction": "inbound",
    "tags": ["website-lead", "vip"]
  }
}

message.sent#

Fires when an outbound message is confirmed sent by the provider.

{
  "event": "message.sent",
  "timestamp": "2026-03-27T14:35:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "messageId": "msg457",
    "conversationId": "conv789",
    "channel": "sms",
    "status": "sent",
    "externalId": "SM1234567890abcdef"
  }
}

message.delivered#

Fires when an outbound message is confirmed delivered to the recipient.

{
  "event": "message.delivered",
  "timestamp": "2026-03-27T14:36:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "messageId": "msg457",
    "conversationId": "conv789",
    "channel": "sms",
    "status": "delivered",
    "externalId": "SM1234567890abcdef"
  }
}

message.failed#

Fires when an outbound message fails to deliver or bounces.

{
  "event": "message.failed",
  "timestamp": "2026-03-27T14:37:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "messageId": "msg458",
    "conversationId": "conv790",
    "channel": "email",
    "status": "bounced",
    "externalId": "postmark-msg-id-123"
  }
}

appointment.booked#

Fires when a new appointment is booked.

{
  "event": "appointment.booked",
  "timestamp": "2026-03-27T14:38:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "appointmentId": "appt001",
    "calendarId": "cal001",
    "appointmentTypeId": "type001",
    "appointmentTitle": "30-Min Consultation",
    "startTime": 1743508800000,
    "endTime": 1743510600000,
    "status": "scheduled"
  }
}

appointment.cancelled#

Fires when an appointment is cancelled.

{
  "event": "appointment.cancelled",
  "timestamp": "2026-03-27T14:45:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "appointmentId": "appt001",
    "status": "cancelled"
  }
}

appointment.rescheduled#

Fires when an appointment is moved to a new time.

{
  "event": "appointment.rescheduled",
  "timestamp": "2026-03-27T14:46:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "appointmentId": "appt001",
    "startTime": 1743595200000,
    "endTime": 1743597000000,
    "status": "scheduled"
  }
}

appointment.confirmed#

Fires when an appointment is confirmed.

{
  "event": "appointment.confirmed",
  "timestamp": "2026-03-27T14:47:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "appointmentId": "appt001",
    "status": "confirmed"
  }
}

appointment.completed#

Fires when an appointment is marked completed.

{
  "event": "appointment.completed",
  "timestamp": "2026-03-27T14:48:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "appointmentId": "appt001",
    "status": "completed"
  }
}

appointment.no_show#

Fires when an appointment is marked as a no-show.

{
  "event": "appointment.no_show",
  "timestamp": "2026-03-27T14:49:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "appointmentId": "appt001",
    "status": "no_show"
  }
}

opportunity.created#

Fires when a new opportunity is created in a pipeline.

{
  "event": "opportunity.created",
  "timestamp": "2026-03-27T14:39:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "opportunityId": "opp001",
    "pipelineId": "pipe001",
    "stageId": "stage001",
    "title": "Website Redesign",
    "value": 5000
  }
}

opportunity.stage_changed#

Fires when an opportunity moves to a different pipeline stage.

{
  "event": "opportunity.stage_changed",
  "timestamp": "2026-03-27T14:40:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "opportunityId": "opp001",
    "pipelineId": "pipe001",
    "fromStageId": "stage001",
    "toStageId": "stage002",
    "title": "Website Redesign"
  }
}

opportunity.won#

Fires when an opportunity is marked as won.

{
  "event": "opportunity.won",
  "timestamp": "2026-03-27T14:41:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "opportunityId": "opp001",
    "pipelineId": "pipe001",
    "title": "Website Redesign",
    "value": 5000
  }
}

opportunity.lost#

Fires when an opportunity is marked as lost.

{
  "event": "opportunity.lost",
  "timestamp": "2026-03-27T14:42:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "opportunityId": "opp001",
    "pipelineId": "pipe001",
    "title": "Website Redesign",
    "value": 5000,
    "lostReason": "Budget constraints"
  }
}

opportunity.updated#

Fires when an opportunity record is updated.

{
  "event": "opportunity.updated",
  "timestamp": "2026-03-27T14:50:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "opportunityId": "opp001",
    "pipelineId": "pipe001",
    "name": "Website Redesign",
    "value": 6500,
    "changedFields": ["value"]
  }
}

The changedFields array lists the names of the fields that changed in this update.


opportunity.deleted#

Fires when an opportunity is deleted.

{
  "event": "opportunity.deleted",
  "timestamp": "2026-03-27T14:51:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "opportunityId": "opp001",
    "pipelineId": "pipe001",
    "name": "Website Redesign"
  }
}

form.submitted#

Fires when a public form is submitted.

{
  "event": "form.submitted",
  "timestamp": "2026-03-27T14:43:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "formId": "form001",
    "submissionId": "sub001",
    "formName": "Contact Us",
    "fields": {
      "name": "Jane Doe",
      "email": "[email protected]",
      "message": "I'd like more info about your services"
    }
  }
}

invoice.paid#

Fires when an invoice payment is received.

{
  "event": "invoice.paid",
  "timestamp": "2026-03-27T14:44:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "invoiceId": "inv001",
    "amount": 1500,
    "currency": "usd",
    "paidAt": "2026-03-27T14:44:00.000Z"
  }
}

task.created#

Fires when a new task is created.

{
  "event": "task.created",
  "timestamp": "2026-03-27T14:52:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "taskId": "task001",
    "title": "Follow up with Jane",
    "status": "todo",
    "priority": "high",
    "dueDate": 1743595200000,
    "assignedTo": "user001"
  }
}

task.updated#

Fires when a task record is updated.

{
  "event": "task.updated",
  "timestamp": "2026-03-27T14:53:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "taskId": "task001",
    "title": "Follow up with Jane",
    "status": "in_review",
    "priority": "high",
    "changedFields": ["status"]
  }
}

The changedFields array lists the names of the fields that changed in this update.


task.completed#

Fires when a task is marked completed.

{
  "event": "task.completed",
  "timestamp": "2026-03-27T14:54:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "taskId": "task001",
    "title": "Follow up with Jane",
    "status": "completed",
    "priority": "high",
    "completedAt": 1743596400000
  }
}

task.deleted#

Fires when a task is deleted.

{
  "event": "task.deleted",
  "timestamp": "2026-03-27T14:55:00.000Z",
  "data": {
    "subAccountId": "sa_001",
    "contactId": "abc123",
    "taskId": "task001",
    "title": "Follow up with Jane",
    "status": "todo",
    "priority": "high"
  }
}

Payload shapes may vary slightly depending on the data available at the time of the event. Fields shown as present in examples may be null if the corresponding data was not set. The contactId field is included when the event is associated with a specific contact.

Was this page helpful?