Agent skill
Aimfox Webhooks Skill
Receive and authenticate Aimfox webhooks. Use when setting up an Aimfox webhook endpoint, checking the Authentication Header, debugging 401s, or handling LinkedIn automation events like new_connection, accepted, new_reply, message and account_logged_out.
Install this skill
npx skills add hookdeck/webhook-skills --skill aimfox-webhooks
Aimfox (LinkedIn automation) sends a JSON webhook to your endpoint when something happens in a workspace: a connection is made or accepted, a lead replies, an account is logged out, a campaign starts or ends. Workspace webhooks are in beta. Webhooks are not signed. The only authentication is the optional Authentication Header you add to the webhook (Basic, or a custom header such as a Bearer token), which Aimfox sends with every request.
When to Use This Skill
- How do I receive Aimfox webhooks?
- How do I verify the Aimfox webhook Authentication Header?
- Does Aimfox sign webhook payloads? (No: use a shared credential header)
- How do I handle
new_connection,accepted,new_replyoraccount_logged_outevents? - Why does my Aimfox webhook endpoint return 401?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyAimfoxRequest(headers, secret = process.env.AIMFOX_WEBHOOK_SECRET) {
if (!secret) return false; // never accept when unconfigured
const name = (process.env.AIMFOX_WEBHOOK_HEADER || 'Authorization').toLowerCase();
const received = headers[name]; // Node lowercases header names
return Boolean(received) && safeEqual(received, secret); // whole value, no prefix parsing
}
- The header name is whatever you typed into Authentication Header in Aimfox. Set
AIMFOX_WEBHOOK_HEADERto that name (defaultAuthorization) andAIMFOX_WEBHOOK_SECRETto the exact, whole header value, including anyBearerorBasicprefix. Do not strip or parse the prefix. - Look the header up case-insensitively. Return
401when it is missing or differs. - A static shared credential authenticates the sender, not the body. There is no signature and no signed timestamp, so nothing stops a captured request being replayed. Use HTTPS and rotate the credential if it leaks.
- Read the body raw (
express.raw({ type: '*/*' })) and authenticate before parsing it. Return400for a body that isn't a JSON object,200once handled.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Event Types
Every payload has id, event_type, workspace and an event object whose fields depend on the type. Aimfox retries failed deliveries up to 6 times with exponential backoff, so handle duplicates by id.
event_type | Sent When |
|---|---|
account_logged_in | A workspace LinkedIn account logs in and is authenticated |
account_logged_out | A workspace account is logged out (manually or session expiry) |
new_connection | A new LinkedIn connection is detected on a workspace account |
view | A campaign visits a LinkedIn profile |
connect | A campaign sends a connection request |
accepted | A campaign connection request is accepted |
inmail | A campaign sends an InMail |
message | A campaign sends a message |
new_reply | A reply arrives to any LinkedIn message on a workspace account |
reply | A lead sends their first reply to a campaign message |
inmail_reply | A lead replies to a campaign InMail |
lead_label_added | A label is applied to a lead |
campaign_created | A campaign is created |
campaign_started | A campaign starts for the first time |
campaign_ended | A campaign runs to completion |
inbox_event | Any inbox activity on a workspace account |
campaign_reply | A lead replies to any campaign message |
Environment Variables
AIMFOX_WEBHOOK_HEADER=Authorization # the header name you set in Aimfox
AIMFOX_WEBHOOK_SECRET="Bearer your-secret" # the exact whole header value
Local Development
Aimfox can't reach localhost. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 aimfox --path /webhooks/aimfox
Use the printed URL as the webhook URL in Aimfox.
Resources
- references/overview.md - What Aimfox webhooks are, events and payload shape
- references/setup.md - Add a webhook and an Authentication Header
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests