For AI agents: a documentation index is available at the root level at /llms.txt and /llms-full.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.
DashboardGet API Key
GuidesAPI ReferenceCommon Patterns
  • Getting Started
    • Welcome
    • Introduction
    • Quickstart
    • Authentication
  • Core Concepts
    • Sessions & Cookies
    • Profiles
    • Messaging
    • Posts & Engagement
    • Connections
    • Search
    • SalesNav
    • SalesNav Filters
  • Playbooks
    • Multi-Step Workflows
    • Query Patterns
  • Workflow Guides
    • Lead Discovery
    • Warm Outreach
    • Content Intelligence
    • Company Research
    • Job Market Intelligence
    • Webhooks
  • Integration
    • MCP Server
    • WebSocket Relay
DashboardGet API Key
On this page
  • Webhooks
  • Create a Webhook
  • Available Event Types
  • Inbound Signals
  • Action Results
  • System Events
  • Payload Format
  • HMAC Signature Verification
  • Verification Example (Node.js)
  • Slack-Native Formatting
  • Recommended Channel Split
  • List Webhooks
  • Delete a Webhook
  • Test a Webhook
  • Delivery Log
  • How Events Are Generated
  • Related
Workflow Guides

Webhooks

Was this page helpful?
Previous

MCP Server

Next
Built with

Webhooks

Voyager can push real-time events to your server via webhooks. Instead of polling for new messages or connection requests, configure a webhook URL and Voyager will POST events as they happen. Payloads are signed with HMAC-SHA256, and Slack-formatted delivery is built in.

Management endpoints return the { success, statusCode, message, data, errors } envelope. The JSON examples below show only the endpoint-specific payload — access res.data.<field> in your client. Webhook deliveries to your server use the raw event shape below, not the envelope.

Create a Webhook

$curl -X POST "$BASE/api/webhooks" \
> -H "Authorization: Bearer $KEY" \
> -H "X-User-Id: $USER" \
> -H "Content-Type: application/json" \
> -d '{
> "url": "https://your-server.com/webhook",
> "events": ["message_received", "connection_received", "session_expired"],
> "secret": "your-hmac-secret"
> }'
1{
2 "success": true,
3 "webhook": {
4 "id": "550e8400-e29b-41d4-a716-446655440000",
5 "url": "https://your-server.com/webhook",
6 "events": ["message_received", "connection_received", "session_expired"],
7 "secret": "***",
8 "active": true,
9 "createdAt": "2026-03-17T10:00:00Z",
10 "updatedAt": "2026-03-17T10:00:00Z"
11 }
12}

Note: id is a UUID, and the secret you sent is masked to "***" in every response — Voyager never echoes it back. Save it on your side when you create the webhook.

Available Event Types

Inbound Signals

EventDescription
message_receivedNew message in your inbox
connection_receivedNew inbound connection request
post_likedSomeone liked your post
post_commentedSomeone commented on your post
comment_repliedSomeone replied to your comment

Action Results

EventDescription
action_send_completedMessage send succeeded
action_send_failedMessage send failed
action_connect_completedConnection request succeeded
action_connect_failedConnection request failed
action_like_completedLike action succeeded
action_comment_completedComment action succeeded
action_post_completedPost creation succeeded
action_visit_completedProfile visit succeeded

System Events

EventDescription
session_expiredLinkedIn session expired, needs re-authentication
job_completedAsync job finished successfully
job_failedAsync job failed

Payload Format

All webhook payloads follow this structure:

1{
2 "id": "550e8400-e29b-41d4-a716-446655440000",
3 "event": "message_received",
4 "tenantId": "32e365dc-e8c4-4caa-8637-0e6b48dfeccd",
5 "tenantName": "Acme",
6 "timestamp": "2026-03-17T10:30:00Z",
7 "data": {
8 "userId": "charis",
9 "conversationId": "conv123",
10 "participantName": "Jane Smith",
11 "participantUrn": "urn:li:fsd_profile:ACoAA...",
12 "lastMessage": "Thanks for connecting! Would love to chat about..."
13 }
14}
  • id is a UUID per delivery — useful for idempotency on your side
  • event is one of the types in the catalogue above
  • tenantName is the human-readable tenant label
  • data varies by event type; the userId of the LinkedIn account the event belongs to lives inside data (not at the top level)

HMAC Signature Verification

Voyager signs every webhook payload with HMAC-SHA256 using the secret you provided at creation. The signature is in the X-Webhook-Signature header.

Verification Example (Node.js)

1const crypto = require('crypto');
2
3function verifyWebhook(payload, signature, secret) {
4 const expected = crypto
5 .createHmac('sha256', secret)
6 .update(JSON.stringify(payload))
7 .digest('hex');
8 return crypto.timingSafeEqual(
9 Buffer.from(signature),
10 Buffer.from(expected)
11 );
12}
13
14// In your webhook handler
15app.post('/webhook', (req, res) => {
16 const sig = req.headers['x-webhook-signature'];
17 if (!verifyWebhook(req.body, sig, 'your-hmac-secret')) {
18 return res.status(401).send('Invalid signature');
19 }
20 // Process the event
21 console.log(req.body.event, req.body.data);
22 res.status(200).send('OK');
23});

Always verify the HMAC signature before processing webhook payloads. Without verification, an attacker could send fake events to your endpoint.

Slack-Native Formatting

If your webhook URL points to a Slack incoming webhook (hooks.slack.com), Voyager automatically formats payloads as Slack messages with rich formatting — no middleware needed.

$curl -X POST "$BASE/api/webhooks" \
> -H "Authorization: Bearer $KEY" \
> -H "X-User-Id: $USER" \
> -H "Content-Type: application/json" \
> -d '{
> "url": "https://hooks.slack.com/services/T.../B.../xxx",
> "events": ["message_received", "connection_received", "session_expired"]
> }'

For Slack webhooks, you do not need to provide a secret — Slack handles authentication via the webhook URL itself. Voyager detects hooks.slack.com URLs and switches to Slack message format automatically.

Recommended Channel Split

For production setups, use two webhooks:

  1. Main channel — inbound signals: message_received, connection_received, post_liked, post_commented, comment_replied, session_expired
  2. Test/ops channel — action results: action_send_completed, action_send_failed, action_connect_completed, action_connect_failed, job_completed, job_failed

This prevents test runs and action confirmations from drowning out real inbound signals.

List Webhooks

$curl "$BASE/api/webhooks" \
> -H "Authorization: Bearer $KEY" \
> -H "X-User-Id: $USER"

Delete a Webhook

$curl -X DELETE "$BASE/api/webhooks/<webhookId>" \
> -H "Authorization: Bearer $KEY" \
> -H "X-User-Id: $USER"

Test a Webhook

Send a test payload to verify your endpoint is working:

$curl -X POST "$BASE/api/webhooks/<webhookId>/test" \
> -H "Authorization: Bearer $KEY" \
> -H "X-User-Id: $USER"

Delivery Log

Check recent delivery attempts and their HTTP status codes:

$curl "$BASE/api/webhooks/deliveries" \
> -H "Authorization: Bearer $KEY" \
> -H "X-User-Id: $USER"

How Events Are Generated

Voyager’s background PollService checks for new messages, connection requests, and notifications every 5 minutes per tenant. When it detects changes, it fires the corresponding webhook events.

The first poll after a session sync takes a state snapshot without firing events — this prevents a flood of historical events on first connect. Only subsequent polls emit deltas.

If all three poll sections (messages, invitations, notifications) fail simultaneously with session errors, Voyager emits a session_expired event and pauses polling for that user until the next cookie sync.

Related

  • Sessions — Session expiry and recovery
  • Messaging — Message events
  • Connections — Connection events