Webhooks Overview

Receive workflow, account, and LinkedIn activity events in real time. Instead of polling for a workflow result, register a webhook once and Linked API delivers an event to your endpoint whenever a workflow changes state, a LinkedIn account changes status, a message is observed in a monitored inbox, or a connection changes in a monitored network. Once signals launch, the webhook will also receive an event whenever a signal detects something new.

How it works

  • You register a single endpoint URL that receives events. A client may hold one active webhook at a time.
  • Every event your webhook receives is delivered as an HTTP POST with a JSON body (the event envelope below). By default that is every event; you can choose which events it receives.
  • Respond with any 2xx status to acknowledge. A non-2xx response, or a timeout, is retried with exponential backoff for up to 8 attempts before the delivery is marked failed.
  • Two optional settings secure the delivery: custom headers, so an endpoint that requires authentication lets us in, and a signature, so you can prove a delivery came from Linked API. Both are off by default and neither changes anything until you turn it on.

Set this up either way: in the platform dashboard, or programmatically through the Admin API. Both reach the same webhook, so you can register it in the dashboard and change it from code, or the other way round.

Event envelope

Every delivery has the same shape:

json
{
  "id": "workflow.completed:wf-64835e7c-...",
  "type": "workflow.completed",
  "createdAt": "2026-06-25T12:00:00.000Z",
  "data": {
    "workflowId": "wf-64835e7c-...",
    "accountId": "f9b4346a-...",
    "status": "completed",
    "result": { }
  }
}
  • id – stable, unique event identifier. Use it to deduplicate: a retried delivery reuses the same id.
  • type – event type, one of the event types.
  • createdAt – ISO 8601 timestamp of when the event was produced.
  • data – event-specific payload, described per event type on Event Types.

Delivery order is best-effort and not guaranteed. Correlate events by type, data.status, and createdAt rather than by arrival order.

Payload modes

The payload mode controls how much data workflow.completed carries:

  • fat (default) – the full workflow result is inlined in data.result.
  • thin – data.result is omitted; fetch the result via the workflow API using data.workflowId.

You can switch the mode at any time when managing the webhook.

Receiving events

Your endpoint should:

  1. If signing is on, verify the signature against the raw body, before parsing it.
  2. Read the JSON body and branch on type.
  3. Respond 2xx quickly to acknowledge. Do heavy work asynchronously – a slow endpoint will time out and be retried.
  4. Deduplicate on the envelope id. Deliveries are at-least-once, so the same event can arrive more than once.

Without an SDK the endpoint is a few lines in any language. Here it is in Node.js with Express, reading the body unparsed with express.raw() and checking it with the reference verifyWebhookSignature from Signature Verification:

const express = require("express")
// The reference implementation from Signature Verification, saved next to this file as verify-webhook-signature.js.
const { verifyWebhookSignature } = require("./verify-webhook-signature")

const app = express()
app.use(express.raw({ type: "application/json" }))

app.post("/hooks/linkedapi", (req, res) => {
  // Skip this check if signing is off for your webhook.
  const isAuthentic = verifyWebhookSignature({
    rawBody: req.body,
    timestamp: req.headers["linked-api-signature-timestamp"],
    signature: req.headers["linked-api-signature"],
    secret: process.env.LINKED_API_WEBHOOK_SECRET,
    nowSeconds: Math.floor(Date.now() / 1000),
  })
  if (!isAuthentic) {
    res.sendStatus(401)
    return
  }

  const event = JSON.parse(req.body.toString("utf8"))

  switch (event.type) {
    case "workflow.completed":
      console.log(`Workflow ${event.data.workflowId} finished: ${event.data.status}`)
      console.log("Result:", event.data.result) // present in fat mode only
      break
    case "account.reconnectionRequired":
      console.log(`Account ${event.data.accountId} needs reconnection.`)
      break
    case "inbox.messageReceived":
      // personHashedUrl is always present; personPublicUrl is null until the person is resolved.
      console.log(`New message from ${event.data.personPublicUrl ?? event.data.personHashedUrl}: ${event.data.text}`)
      break
    case "inbox.messageSent":
      console.log(`Outgoing message in thread ${event.data.threadId}`)
      break
    case "network.connectionAccepted":
      console.log(`${event.data.personUrl} accepted your connection request`)
      break
    case "network.connectionRequestReceived":
      console.log(`New connection request from ${event.data.personUrl}`)
      break
    case "webhook.test":
      console.log("Test event:", event.data.message)
      break
  }

  // Acknowledge fast. A non-2xx response makes Linked API retry with backoff.
  res.sendStatus(200)
})