Webhooks
Manage your outbound webhook and parse incoming deliveries into typed events. See the Admin overview for how to create the admin client, and the Webhooks API for the underlying event model.
Webhook management lives on the admin client (admin.webhooks); event parsing is a standalone helper exported from the package root.
Register a webhook
A client may hold one active webhook. It receives every event Linked API emits.
const webhook = await admin.webhooks.set({
url: 'https://example.com/hooks/linkedapi',
payloadMode: 'fat', // 'fat' inlines the workflow result; 'thin' sends a reference only
});
console.log(`Webhook ${webhook.id} → ${webhook.url}`);Params
url– HTTPS endpoint that will receive deliveries.payloadMode/payload_mode– optional,"fat"(default) or"thin".
Data
id– webhook subscription id.url– the registered destination.payloadMode/payload_mode– current payload mode.isActive/is_active– whether the webhook is active.createdAt/created_at– ISO 8601 timestamp.
There is no update method – to change the destination, delete the webhook and set a new one.
Get the active webhook
const webhooks = await admin.webhooks.get();
console.log(webhooks[0]?.url ?? 'no active webhook');Returns an array with at most one entry.
Change payload mode
await admin.webhooks.setPayloadMode({ id: 'whs-...', payloadMode: 'thin' });Delete the webhook
Soft delete: the delivery history is preserved and pending deliveries are dropped. You can register a fresh webhook afterwards.
await admin.webhooks.delete({ id: 'whs-...' });Inspect deliveries
Return the most recent deliveries (newest first) as a debug feed.
const deliveries = await admin.webhooks.deliveries();
for (const delivery of deliveries) {
console.log(`${delivery.eventType} → ${delivery.status} (attempts: ${delivery.attempts})`);
if (delivery.lastError) {
console.log(` last error: ${delivery.lastError} (HTTP ${delivery.responseStatusCode})`);
}
}Data
Each delivery has the following shape:
interface TWebhookDelivery {
id: string; // delivery identifier, passed to replayDelivery
eventType: TWebhookEventType; // the event type that was delivered
eventId: string; // the envelope id of the delivered event
status: 'pending' | 'delivering' | 'success' | 'failed';
attempts: number; // delivery attempts made so far
responseStatusCode: number | null; // HTTP status your endpoint returned on the last attempt
lastError: string | null; // error text from the last failed attempt
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
}Replay a delivery
Re-arm an already-settled delivery. The same event id is reused, so your idempotency handling still applies.
await admin.webhooks.replayDelivery({ deliveryId: 'whd-...' });Send a test event
Emit a synthetic webhook.test event to verify a freshly registered endpoint end-to-end.
await admin.webhooks.sendTest();Receiving and parsing events
Use parseWebhookEvent / parse_webhook_event to turn a raw request body into a typed, discriminated event. Pass the raw body exactly as received, then branch on type to narrow data.
import express from 'express';
import { parseWebhookEvent } from '@linkedapi/node';
const app = express();
app.use(express.raw({ type: 'application/json' }));
app.post('/hooks/linkedapi', (req, res) => {
const event = parseWebhookEvent(req.body as Buffer);
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 the public one and
// is null until that person has been 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);
});parseWebhookEvent / parse_webhook_event throws when the body is not valid JSON or is missing the id / type envelope fields. Deduplicate on event.id – deliveries are at-least-once, so the same event can arrive more than once.
The returned union (TWebhookEvent in Node, WebhookEvent in Python) covers workflow events, account events, inbox message events, network events, and the test event. See the Webhooks API for the full event catalog.
Inbox message events carry two URLs for the other participant: personHashedUrl, the hashed one, always present and accepted by every method that takes a person URL; and personPublicUrl, the public one, which is null on any message whose person has not been resolved – including the first message in a thread. See pollInbox for how it is resolved. The payload also still carries personUrl, deprecated and replaced by personHashedUrl – it holds the same value and stays only so existing subscribers keep working.
Errors
All webhook methods throw LinkedApiError on failure – see Admin overview. For the complete HTTP reference, see the Webhooks API docs.