Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vibefollow.com/llms.txt

Use this file to discover all available pages before exploring further.

Every webhook delivery shares an envelope:
interface WebhookEvent {
  type: 'email.opened' | 'email.clicked' | 'email.bounced' | 'email.replied' | 'email.unsubscribed';
  id: string;       // unique per delivery; idempotent
  created: number;  // Unix seconds when Vibefollow signed the payload
  data: { ... };    // type-specific
}
id is unique per delivery and stable across retries — dedupe on it if you’re persisting events.

Event catalog

Fires when the recipient opens an email Vibefollow sent on your behalf.
Tracking pixels are stripped if the recipient’s mail client blocks them — do not assume a missing open means the email wasn’t read.
TypeScript shape
interface EmailOpenedEvent {
  type: 'email.opened';
  id: string;
  created: number;
  data: {
    draftId: string;
    projectId: string;
    externalUserId: string;
    messageId: string;     // Postmark message id
    openedAt: string;      // ISO 8601
  };
}
Example payload
{
  "type": "email.opened",
  "id": "evt_01HZ8X3F4T5K9N2M6P7R8Q1B2C",
  "created": 1747526400,
  "data": {
    "draftId": "drf_42",
    "projectId": "prj_acme",
    "externalUserId": "usr_42",
    "messageId": "msg_abcdef",
    "openedAt": "2026-05-17T14:00:00.000Z"
  }
}
Fires when the recipient clicks any tracked link in the email. One event per click — the same recipient clicking three links produces three events.TypeScript shape
interface EmailClickedEvent {
  type: 'email.clicked';
  id: string;
  created: number;
  data: {
    draftId: string;
    projectId: string;
    externalUserId: string;
    messageId: string;
    url: string;           // the destination URL
    clickedAt: string;     // ISO 8601
  };
}
Example payload
{
  "type": "email.clicked",
  "id": "evt_01HZ8X4Q2W3E5R6T8Y9U0I1O2P",
  "created": 1747526460,
  "data": {
    "draftId": "drf_42",
    "projectId": "prj_acme",
    "externalUserId": "usr_42",
    "messageId": "msg_abcdef",
    "url": "https://acme.io/onboarding/step-3",
    "clickedAt": "2026-05-17T14:01:00.000Z"
  }
}
Fires when Postmark reports a bounce or spam complaint. bounceType distinguishes the kind so you can decide whether to suppress the address.TypeScript shape
interface EmailBouncedEvent {
  type: 'email.bounced';
  id: string;
  created: number;
  data: {
    draftId: string;
    projectId: string;
    externalUserId: string;
    messageId: string;
    bounceType: 'hard' | 'soft' | 'transient' | 'complaint' | 'unknown';
    reason?: string;       // human-readable explanation
    bouncedAt: string;     // ISO 8601
  };
}
Bounce type guide
bounceTypeWhat it meansAction
hardPermanent failure — address invalidSuppress; don’t email again
softTemporary — mailbox full, server timeoutVibefollow retries; no action needed
transientRouting hiccupVibefollow retries
complaintMarked as spamSuppress; also indicates content/relevance issue
unknownPostmark couldn’t classifyInvestigate manually
Fires when the recipient replies to a Vibefollow-sent email. The full plain-text body is included, along with a tone classification used internally to auto-pause sends to users who reply negatively.TypeScript shape
interface EmailRepliedEvent {
  type: 'email.replied';
  id: string;
  created: number;
  data: {
    draftId: string;
    projectId: string;
    externalUserId: string;
    messageId: string;
    replyBody: string;     // plain text, no signature stripping
    tone: 'positive' | 'neutral' | 'negative' | 'unsubscribe_request';
    receivedAt: string;    // ISO 8601
  };
}
Tone classifications
toneVibefollow’s reaction
positiveContinue lifecycle as normal
neutralContinue lifecycle as normal
negativeAuto-pause further sends to this user
unsubscribe_requestTreat as unsubscribe; also emits email.unsubscribed
Vibefollow does not auto-reply on your behalf in v1. Inbound replies route to your project’s reply-to address; the webhook is a copy for your systems to react to.
Fires when the recipient unsubscribes — through the one-click List-Unsubscribe header (RFC 8058), through a reply that scored as unsubscribe_request, or through a manual flag in the dashboard.TypeScript shape
interface EmailUnsubscribedEvent {
  type: 'email.unsubscribed';
  id: string;
  created: number;
  data: {
    projectId: string;
    externalUserId: string;
    email: string;
    source: 'one_click' | 'reply_request' | 'manual';
    unsubscribedAt: string; // ISO 8601
  };
}
Source values
sourceTriggered by
one_clickGmail / Outlook one-click unsubscribe button
reply_requestInbound reply tone-classified as unsubscribe
manualFounder flagged the user in the dashboard
Unsubscribed users are excluded from every future trigger evaluation. You don’t need to do anything — this webhook is informational so your CRM can mirror the state.