Documentation

Webhooks

Receive every event at your own URL as it happens.

Adding a webhook

Add one in Settings, with Create a webhook, or with the needleCreateWebhook MCP tool. Any member of the organization can manage webhooks. An organization can have up to 5, and each one gets every event type.

The URL must start with https://. When you add a webhook you get its signing secret, which starts with whsec_. It is shown only this once, so store it where your receiver can read it.

What Needle sends

Each event is one POST with a JSON body:

{
  "version": "v1",
  "id": "00000000-0000-0000-0000-000000000001",
  "type": "linkedin_message_received",
  "timestamp": "2026-01-01T12:00:00.000Z",
  "data": {
    "id": "00000000-0000-0000-0000-000000000001",
    "type": "linkedin_message_received",
    "label": "Reply received",
    "occurredAt": "2026-01-01T12:00:00.000Z",
    "channel": "linkedin",
    "nodeId": null,
    "leadId": "00000000-0000-0000-0000-000000000002",
    "leadName": "Jane Doe",
    "leadImageUrl": null,
    "campaignId": "00000000-0000-0000-0000-000000000003",
    "campaignName": "Example campaign",
    "enrollmentId": "00000000-0000-0000-0000-000000000004",
    "connectedAccountId": "00000000-0000-0000-0000-000000000005",
    "accountName": "John Doe",
    "data": { "text": "Hello!" }
  }
}

data is the same event object that Search events returns. timestamp is when the event happened.

Each request also has these headers:

  • webhook-id: the event id. It stays the same when a request is sent again.
  • webhook-timestamp: when this request was sent, in Unix seconds.
  • webhook-signature: v1, followed by a base64 HMAC-SHA256 of {webhook-id}.{webhook-timestamp}.{body}, keyed with your secret.

Verifying requests

The headers follow Standard Webhooks, so its libraries can check them. A request that fails the check did not come from Needle. The libraries also refuse a request whose webhook-timestamp is more than 5 minutes from your server's clock, so a captured request can't be sent again later.

Verify a request

Pass the raw body, before any JSON parsing, and the request headers to verify().

$ npm install express standardwebhooks
import express from 'express'
import { Webhook } from 'standardwebhooks'

const webhook = new Webhook(process.env.NEEDLE_WEBHOOK_SECRET)
const app = express()

app.post('/webhooks/needle', express.raw({ type: 'application/json' }), (req, res) => {
  let event
  try {
    event = webhook.verify(req.body, req.headers)
  } catch {
    return res.status(400).end()
  }
  res.status(204).end()
  // Handle the event here, after answering.
})

Standard Webhooks has libraries for other languages.

Retries

An event fails when your URL doesn't answer with a 2xx within 10 seconds. A failed event is tried 3 times: right away, 10 minutes later, and 1 hour after the first try. After the 3rd failure it is not sent again. An event that has not been delivered after 3 days is given up without further tries.

Missed events are still in the event log. Fetch them with Search events.

Duplicates and order

Delivery is at least once: after a crash on our side, an event you already got can arrive again. Use webhook-id to skip events you have already handled.

Events are not sent in order, and a retried event can arrive after newer ones. Use timestamp for the order in which things happened.

Changing or removing a webhook

Change the URL in Settings, or with Change a webhook URL. Events not yet delivered go to the new URL, and the secret stays the same.

Removing a webhook stops its deliveries, and events not yet delivered to it are dropped. If you lose the secret, remove the webhook and add it again to get a new one.

Event types

TypeWhen it is sent
lead_enrolledA lead was added to a campaign.
lead_skippedA lead was not added to a campaign; data.skipReason says why.
message_generatedAI wrote a message for a step.
condition_evaluatedA check in the sequence was evaluated.
task_heldA step is waiting for approval.
task_approvedA held step was approved.
task_skippedA step was skipped; data.skipReason says why.
task_cancelledA step was cancelled before it ran.
task_failedA step failed; data.errorKind says why.
enrollment_waitingThe sequence is waiting, for example for a reply.
enrollment_completedThe lead reached the end of the sequence.
enrollment_stoppedThe sequence stopped early; data.exitReason says why.
lead_suppressedA lead matched a suppression.
account_pausedA connected account was paused after hitting a limit.
campaign_status_changedA campaign's status changed, for example it was started or paused.
linkedin_profile_visitedA LinkedIn profile was visited.
linkedin_invite_sentA LinkedIn connection request was sent.
linkedin_invite_withdrawnA LinkedIn connection request was withdrawn.
linkedin_message_sentA LinkedIn message was sent.
linkedin_follow_sentA LinkedIn profile was followed.
linkedin_unfollowedA LinkedIn profile was unfollowed.
linkedin_invite_acceptedA LinkedIn connection request was accepted.
linkedin_message_receivedA LinkedIn reply was received.
linkedin_account_status_changedA LinkedIn connected account changed status, for example it was disconnected.
linkedin_post_commentedA comment was posted on LinkedIn.
linkedin_post_reactedA reaction was added to a LinkedIn post.
instagram_message_sentAn Instagram message was sent.
instagram_profile_visitedAn Instagram profile was visited.
instagram_follow_sentAn Instagram profile was followed.
instagram_unfollowedAn Instagram profile was unfollowed.
instagram_message_receivedAn Instagram reply was received.
instagram_account_status_changedAn Instagram connected account changed status, for example it was disconnected.
instagram_post_commentedA comment was posted on Instagram.
instagram_post_reactedAn Instagram post was liked.
instagram_message_reactedA reaction was added to an Instagram message.
instagram_post_createdAn Instagram post was published.

Versioning

Every body has "version": "v1". Within v1, new event types and new fields can be added, but fields are not removed or renamed. Ignore types and fields you do not know.

Search docs

Search documentation, API reference, and MCP tools

We use cookies to enhance your experience on Needle and keep your data secure. Privacy Policy