{"_id":"@brainhours/relay-core","_rev":"5-7a036098bf432d9470ccc2e290fec8b8","name":"@brainhours/relay-core","dist-tags":{"latest":"2.1.0"},"versions":{"1.25.1":{"name":"@brainhours/relay-core","version":"1.25.1","keywords":["unipile","twilio","uazapi","messaging","webhooks","linkedin","whatsapp","instagram","telegram","multi-channel"],"author":{"name":"Guilherme Goulart","email":"guilherme.goulart@getraze.co"},"license":"MIT","_id":"@brainhours/relay-core@1.25.1","maintainers":[{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"}],"homepage":"https://github.com/brainhours/relay/tree/main/packages/core#readme","bugs":{"url":"https://github.com/brainhours/relay/issues"},"dist":{"shasum":"7c1f54e7b64f297cfb537b29a80e659eaf0bacf4","tarball":"https://registry.npmjs.org/@brainhours/relay-core/-/relay-core-1.25.1.tgz","fileCount":94,"integrity":"sha512-QX4HNbe3eAhgLF5bccdAjiy+dxPUc5zSMmqHM4TRlf9y56o20Rq0IdyR7axHXzxpNEOr/GVphhNkh7/ZLNZpJg==","signatures":[{"sig":"MEUCIFlE/vMqBN3brSFpfGYgZ6BL+Y0FH53x1eIebzu8rDUuAiEAisde5rT/M7xGGeNAF0YHba97+NqOfcczEZ1OpHkyLig=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":561459},"main":"src/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"require":"./src/index.js"},"./queue":{"require":"./src/queue/index.js"},"./events":{"require":"./src/events/index.js"},"./providers/wati":{"require":"./src/providers/wati/index.js"},"./providers/twilio":{"require":"./src/providers/twilio/index.js"},"./providers/uazapi":{"require":"./src/providers/uazapi/index.js"},"./providers/zernio":{"require":"./src/providers/zernio/index.js"},"./providers/unipile":{"require":"./src/providers/unipile/index.js"},"./providers/webchat":{"require":"./src/providers/webchat/index.js"},"./providers/cloud-api":{"require":"./src/providers/cloud-api/index.js"}},"gitHead":"b8b48d9bebc4dbf8cf7f8630aef13c45dc3b8ac1","_npmUser":{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"},"repository":{"url":"git+https://github.com/brainhours/relay.git","type":"git","directory":"packages/core"},"_npmVersion":"10.9.2","description":"Core messaging integrations - Unipile, Uazapi (WhatsApp), Meta Cloud API (official WhatsApp), Wati (WhatsApp Business), Twilio (SMS/WhatsApp), Zernio (social publishing + inbox + ads), Webchat and more","directories":{},"_nodeVersion":"22.17.1","dependencies":{"axios":"^1.7.9","form-data":"^4.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bull":"^4.0.0","express":"^4.18.0","ioredis":"^5.0.0","express-rate-limit":"^7.0.0"},"peerDependenciesMeta":{"bull":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"express-rate-limit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/relay-core_1.25.1_1785784119721_0.030013498764781987","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@brainhours/relay-core","version":"2.0.0","keywords":["unipile","twilio","uazapi","messaging","webhooks","linkedin","whatsapp","instagram","telegram","multi-channel"],"author":{"name":"Guilherme Goulart","email":"guilherme.goulart@getraze.co"},"license":"MIT","_id":"@brainhours/relay-core@2.0.0","maintainers":[{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"}],"homepage":"https://github.com/brainhours/relay/tree/main/packages/core#readme","bugs":{"url":"https://github.com/brainhours/relay/issues"},"dist":{"shasum":"e94f2cd32aa9b22e221f65d0da91a12a65f8cf20","tarball":"https://registry.npmjs.org/@brainhours/relay-core/-/relay-core-2.0.0.tgz","fileCount":84,"integrity":"sha512-LbF6GvxhvC0xSC5ineWeHFZyjXkwD5amcjsOFEfOngWTW0A/7izYrhMtnmG+o4vjr5dACMPJlbYRkf5E+BF8yA==","signatures":[{"sig":"MEUCIQCJ1JUORel3RAnplmQTqQP6/vycBTCAou1HN1Tz7i4PmgIgcUERj8Fz2bo9nPTDIQKlyZueUoQ1W7ezHM70LrbFsHU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":505275},"main":"src/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"require":"./src/index.js"},"./queue":{"require":"./src/queue/index.js"},"./events":{"require":"./src/events/index.js"},"./providers/wati":{"require":"./src/providers/wati/index.js"},"./providers/twilio":{"require":"./src/providers/twilio/index.js"},"./providers/uazapi":{"require":"./src/providers/uazapi/index.js"},"./providers/zernio":{"require":"./src/providers/zernio/index.js"},"./providers/unipile":{"require":"./src/providers/unipile/index.js"},"./providers/cloud-api":{"require":"./src/providers/cloud-api/index.js"}},"gitHead":"c1aee02ee1d1d4961d1ee5526f0da13bf7a81977","_npmUser":{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"},"repository":{"url":"git+https://github.com/brainhours/relay.git","type":"git","directory":"packages/core"},"_npmVersion":"10.9.2","description":"Core messaging integrations - Unipile, Uazapi (WhatsApp), Meta Cloud API (official WhatsApp), Wati (WhatsApp Business), Twilio (SMS/WhatsApp), Zernio (social publishing + inbox + ads) and more","directories":{},"_nodeVersion":"22.17.1","dependencies":{"axios":"^1.7.9","form-data":"^4.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bull":"^4.0.0","express":"^4.18.0","ioredis":"^5.0.0","express-rate-limit":"^7.0.0"},"peerDependenciesMeta":{"bull":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"express-rate-limit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/relay-core_2.0.0_1785785182872_0.9416427505593272","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@brainhours/relay-core","version":"2.0.1","keywords":["unipile","twilio","uazapi","messaging","webhooks","linkedin","whatsapp","instagram","telegram","multi-channel"],"author":{"name":"Guilherme Goulart","email":"guilherme.goulart@getraze.co"},"license":"MIT","_id":"@brainhours/relay-core@2.0.1","maintainers":[{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"},{"name":"gabriel.engel","email":"gabriel.engel@gmail.com"}],"homepage":"https://github.com/brainhours/relay/tree/main/packages/core#readme","bugs":{"url":"https://github.com/brainhours/relay/issues"},"dist":{"shasum":"2eea691aaf31d5d60fe8f447695d41792dbb7338","tarball":"https://registry.npmjs.org/@brainhours/relay-core/-/relay-core-2.0.1.tgz","fileCount":84,"integrity":"sha512-PHJ75ugbiMlESyVHYSLV0OfngEHECRPgEc5MZHhk5pc92zv/SfS2NoNkwpkC9PJqznAMMB+Lmz4SWBP0GqMvsA==","signatures":[{"sig":"MEUCIQCwaXr9r7C4IyP7ClUmBo+bmnz5EKMvzTWdGNj2C4g7OAIgEny4M5WznR0d6Bhfc83yKJIrcZtHYrQCTJimJzySaf4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":507641},"main":"src/index.js","engines":{"node":">=18.0.0"},"exports":{".":{"require":"./src/index.js"},"./queue":{"require":"./src/queue/index.js"},"./events":{"require":"./src/events/index.js"},"./package.json":"./package.json","./providers/wati":{"require":"./src/providers/wati/index.js"},"./providers/twilio":{"require":"./src/providers/twilio/index.js"},"./providers/uazapi":{"require":"./src/providers/uazapi/index.js"},"./providers/zernio":{"require":"./src/providers/zernio/index.js"},"./providers/unipile":{"require":"./src/providers/unipile/index.js"},"./providers/cloud-api":{"require":"./src/providers/cloud-api/index.js"}},"gitHead":"72851fcb03eecff295a5a508bbb063259711b705","scripts":{"test":"node --test test/*.test.js"},"_npmUser":{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"},"repository":{"url":"git+https://github.com/brainhours/relay.git","type":"git","directory":"packages/core"},"_npmVersion":"10.9.2","description":"Core messaging integrations - Unipile, Uazapi (WhatsApp), Meta Cloud API (official WhatsApp), Wati (WhatsApp Business), Twilio (SMS/WhatsApp), Zernio (social publishing + inbox + ads) and more","directories":{},"_nodeVersion":"22.17.1","dependencies":{"axios":"^1.7.9","form-data":"^4.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"bull":"^4.0.0","express":"^4.18.0","ioredis":"^5.0.0","express-rate-limit":"^7.0.0"},"peerDependenciesMeta":{"bull":{"optional":true},"express":{"optional":true},"ioredis":{"optional":true},"express-rate-limit":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/relay-core_2.0.1_1785814081122_0.1889096510760515","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@brainhours/relay-core","version":"2.1.0","description":"Core messaging integrations - Unipile, Uazapi (WhatsApp), Meta Cloud API (official WhatsApp), Wati (WhatsApp Business), Twilio (SMS/WhatsApp), Zernio (social publishing + inbox + ads) and more","main":"src/index.js","exports":{".":{"require":"./src/index.js"},"./providers/unipile":{"require":"./src/providers/unipile/index.js"},"./providers/uazapi":{"require":"./src/providers/uazapi/index.js"},"./providers/cloud-api":{"require":"./src/providers/cloud-api/index.js"},"./providers/wati":{"require":"./src/providers/wati/index.js"},"./providers/twilio":{"require":"./src/providers/twilio/index.js"},"./providers/zernio":{"require":"./src/providers/zernio/index.js"},"./events":{"require":"./src/events/index.js"},"./queue":{"require":"./src/queue/index.js"},"./package.json":"./package.json"},"keywords":["unipile","twilio","uazapi","messaging","webhooks","linkedin","whatsapp","instagram","telegram","multi-channel"],"repository":{"type":"git","url":"git+https://github.com/brainhours/relay.git","directory":"packages/core"},"author":{"name":"Guilherme Goulart","email":"guilherme.goulart@getraze.co"},"license":"MIT","bugs":{"url":"https://github.com/brainhours/relay/issues"},"homepage":"https://github.com/brainhours/relay/tree/main/packages/core#readme","dependencies":{"axios":"^1.7.9","form-data":"^4.0.1"},"peerDependencies":{"bull":"^4.0.0","express":"^4.18.0","express-rate-limit":"^7.0.0","ioredis":"^5.0.0"},"peerDependenciesMeta":{"bull":{"optional":true},"express":{"optional":true},"express-rate-limit":{"optional":true},"ioredis":{"optional":true}},"engines":{"node":">=18.0.0"},"publishConfig":{"access":"public"},"scripts":{"test":"node --test test/*.test.js"},"_id":"@brainhours/relay-core@2.1.0","gitHead":"673a4843f57cf2a0417f1ed4b34714d6334499f3","_nodeVersion":"22.17.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-KwQ3+UYPFAu6w1MZs7BC+Dw9iM7gIJf/iHd5/jUho273TOm1DO2ohnzXyjNeEzjPdEmuSlkyYngXahEJf3K/sA==","shasum":"7801f87201d1d5b18cd3cd9eb030e1f2b6c7e8fc","tarball":"https://registry.npmjs.org/@brainhours/relay-core/-/relay-core-2.1.0.tgz","fileCount":84,"unpackedSize":509919,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDRxCcP5VWtfb8PwYQ+v+HLNavix60+NqtJ+vjhqRbMggIgGZ6mapraUAVlqN0a1R0qPQ8ALlQzcNNrqX7Axy4tEcU="}]},"_npmUser":{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"},"directories":{},"maintainers":[{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"},{"name":"gabriel.engel","email":"gabriel.engel@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/relay-core_2.1.0_1787271628436_0.4119052853720022"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-03T19:08:39.615Z","modified":"2026-08-21T00:20:28.767Z","1.25.1":"2026-08-03T19:08:39.887Z","2.0.0":"2026-08-03T19:26:23.053Z","2.0.1":"2026-08-04T03:28:01.260Z","2.1.0":"2026-08-21T00:20:28.617Z"},"bugs":{"url":"https://github.com/brainhours/relay/issues"},"author":{"name":"Guilherme Goulart","email":"guilherme.goulart@getraze.co"},"license":"MIT","homepage":"https://github.com/brainhours/relay/tree/main/packages/core#readme","keywords":["unipile","twilio","uazapi","messaging","webhooks","linkedin","whatsapp","instagram","telegram","multi-channel"],"repository":{"type":"git","url":"git+https://github.com/brainhours/relay.git","directory":"packages/core"},"description":"Core messaging integrations - Unipile, Uazapi (WhatsApp), Meta Cloud API (official WhatsApp), Wati (WhatsApp Business), Twilio (SMS/WhatsApp), Zernio (social publishing + inbox + ads) and more","maintainers":[{"name":"guilhermegoulart1","email":"guilherme.goulart@getraze.co"},{"name":"gabriel.engel","email":"gabriel.engel@gmail.com"}],"readme":"# @brainhours/relay-core\n\nCore package for Relay - unified messaging integrations for Node.js.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Environment Variables](#environment-variables)\n- [Quick Start](#quick-start)\n- [Providers](#providers)\n  - [UnipileProvider](#unipile-provider)\n- [API Reference](#api-reference)\n  - [Account Management](#account-management)\n  - [Users](#users)\n  - [Connections](#connections)\n  - [LinkedIn Search](#linkedin-search)\n  - [Search Parameters (Autocomplete)](#search-parameters-autocomplete)\n  - [Messaging](#messaging)\n  - [Webhooks](#webhooks)\n- [Events](#events)\n- [Queue Integration](#queue-integration)\n- [Error Handling](#error-handling)\n- [TypeScript](#typescript)\n- [License](#license)\n\n---\n\n## Installation\n\n```bash\nnpm install @brainhours/relay-core\n```\n\nNo registry setup or authentication needed — the package is published publicly\non [npmjs.com](https://www.npmjs.com/package/@brainhours/relay-core).\n\n### Requirements\n\n- Node.js >= 18.0.0\n- Unipile API credentials (DSN + Access Token)\n\n---\n\n## Environment Variables\n\nCreate a `.env` file in your project root:\n\n```env\n# Required - Unipile Configuration\nUNIPILE_DSN=api1.unipile.com:13111\nUNIPILE_ACCESS_TOKEN=your_access_token_here\n\n# Alternative key name (either works)\nUNIPILE_API_KEY=your_access_token_here\n\n# Optional - For webhook handling\nBACKEND_URL=https://your-api.com\nFRONTEND_URL=https://your-app.com\n```\n\n### How to get Unipile credentials\n\n1. Go to [Unipile Dashboard](https://app.unipile.com)\n2. Create an account or sign in\n3. Navigate to **Settings > API**\n4. Copy your **DSN** and **Access Token**\n\n---\n\n## Quick Start\n\n### Initialize Provider\n\n```javascript\nconst { UnipileProvider } = require('@brainhours/relay-core');\n\n// Load environment variables\nrequire('dotenv').config();\n\n// Initialize provider\nconst provider = new UnipileProvider({\n  dsn: process.env.UNIPILE_DSN,\n  accessToken: process.env.UNIPILE_ACCESS_TOKEN\n});\n\n// Check initialization\nif (!provider.isInitialized()) {\n  console.error('Provider error:', provider.getError());\n  process.exit(1);\n}\n\nconsole.log('Relay initialized successfully!');\n```\n\n### Send a Message\n\n```javascript\n// Send to existing chat\nawait provider.messaging.sendMessage({\n  account_id: 'unipile_account_id',\n  chat_id: 'chat_id',\n  text: 'Hello from Relay!'\n});\n\n// Send to user (creates chat if needed)\nawait provider.messaging.send({\n  account_id: 'unipile_account_id',\n  user_id: 'linkedin_user_id',\n  text: 'Hello!'\n});\n```\n\n### Handle Webhooks\n\n```javascript\nconst { parseWebhook, EventTypes } = require('@brainhours/relay-core');\n\napp.post('/webhooks/unipile', (req, res) => {\n  const event = parseWebhook('unipile', req.body);\n\n  switch (event.type) {\n    case EventTypes.MESSAGE_RECEIVED:\n      console.log('New message from:', event.senderId);\n      console.log('Content:', event.content);\n      break;\n\n    case EventTypes.RELATION_CREATED:\n      console.log('New connection accepted:', event.userId);\n      break;\n  }\n\n  res.status(200).send('OK');\n});\n```\n\n---\n\n## Providers\n\n### Unipile Provider\n\nUnipile provides unified access to multiple messaging platforms:\n\n| Channel | Status | Features |\n|---------|--------|----------|\n| LinkedIn | Stable | Messages, Connections, Search, Invites |\n| WhatsApp | Stable | Messages, Groups, Media |\n| Instagram | Stable | DMs, Media |\n| Telegram | Stable | Messages, Groups |\n| Messenger | Stable | Messages |\n| Email | Stable | IMAP/SMTP |\n\n### Meta WhatsApp Cloud API Provider (v1.10.0+)\n\nOfficial Meta Graph API integration. Stateless, single global endpoint\n(`https://graph.facebook.com/{apiVersion}`), per-call credentials. Multi-tenant\napps load credentials per tenant from their DB and pass them to each call.\n\n```javascript\nconst { MetaCloudApiProvider, parseCloudApiWebhook, validateCloudApiSignature } =\n  require('@brainhours/relay-core');\n\nconst meta = new MetaCloudApiProvider({\n  apiVersion: 'v22.0',\n  appSecret: process.env.META_APP_SECRET\n});\n\n// Send template (works any time)\nawait meta.messaging.sendTemplate({\n  accessToken: creds.accessToken,\n  phoneNumberId: creds.phoneNumberId,\n  to: '5511999999999',\n  templateName: 'lembrete_renovacao',\n  language: 'pt_BR',\n  components: [\n    { type: 'body', parameters: [{ type: 'text', text: 'Joana' }] }\n  ]\n});\n\n// Send free-form text (only inside the 24h customer-service window)\nawait meta.messaging.sendText({\n  accessToken: creds.accessToken,\n  phoneNumberId: creds.phoneNumberId,\n  to: '5511999999999',\n  body: 'Posso ajudar com mais alguma coisa?'\n});\n\n// Templates CRUD (auto-paginates Meta's cursor-based responses)\nconst all = await meta.templates.listAll({\n  accessToken: creds.accessToken,\n  businessAccountId: creds.businessAccountId\n});\n\n// Verify credentials work end-to-end (great for setup screens)\nconst info = await meta.account.verifyConnection({\n  accessToken: creds.accessToken,\n  phoneNumberId: creds.phoneNumberId\n});\n// => { ok: true, displayPhoneNumber, verifiedName, qualityRating, tier }\n```\n\nWebhooks (HMAC-SHA256 validated against the Meta App Secret) parse into\n`NormalizedEvent[]` — one POST often batches multiple events:\n\n```javascript\napp.use('/webhooks/meta', express.json({\n  verify: (req, _res, buf) => { req.rawBody = buf; }\n}));\n\napp.post('/webhooks/meta', (req, res) => {\n  if (!validateCloudApiSignature(req.rawBody, req.headers['x-hub-signature-256'], appSecret)) {\n    return res.sendStatus(401);\n  }\n  for (const ev of parseCloudApiWebhook(req.body)) emitter.emit(ev);\n  res.sendStatus(200);\n});\n```\n\nTwo new EventTypes ship with this provider:\n- `MESSAGE_FAILED` — `statuses[].status === 'failed'` and change-level errors\n- `TEMPLATE_STATUS_CHANGED` — `message_template_status_update` (apps that listen\n  react to template approval/rejection without polling)\n\nPlus opt-in helpers: `effectiveDailyLimit(tier)`, `stableVariant(key)`,\n`isInWindow(lastInboundAt)`. See [docs/providers.md](../../docs/providers.md)\nfor the full reference.\n\n### Uazapi Provider (v1.8.0+)\n\n[Uazapi](https://docs.uazapi.com/) is a Brazilian WhatsApp API. The Uazapi\nprovider supports a **cluster of subscriptions** with heterogeneous capacities\nand pluggable selection strategies, so you can spread WhatsApp instances\nacross multiple Uazapi servers automatically.\n\n```javascript\nconst { UazapiProvider, parseWebhook } = require('@brainhours/relay-core');\n\n// Single-server (simplest)\nconst uazapi = new UazapiProvider({\n  baseUrl: process.env.UAZ_BASE_URL,        // e.g. 'https://free.uazapi.com'\n  adminToken: process.env.UAZ_ADMIN_TOKEN\n});\n\n// Or multi-server with heterogeneous capacities\nconst uazapi = new UazapiProvider({\n  servers: [\n    { id: 'plano-pequeno', baseUrl: 'https://srv1.uazapi.com', adminToken: '...', capacity: 2 },\n    { id: 'plano-medio',   baseUrl: 'https://srv2.uazapi.com', adminToken: '...', capacity: 4 },\n    { id: 'plano-grande',  baseUrl: 'https://srv3.uazapi.com', adminToken: '...', capacity: 10 }\n  ],\n  selectionStrategy: 'weighted-round-robin',  // 2:4:10 distribution\n  getServerLoad: async (serverId) => db.instances.count({ where: { server_id: serverId } })\n});\n\n// Provision a new instance + connect\nconst created = await uazapi.instance.create({ name: 'tenant-acme' });\n// => { id, token, serverId, serverUrl, ... }  (persist these in your DB)\n\nawait uazapi.webhooks.set({\n  token: created.token,\n  serverId: created.serverId,\n  url: `${process.env.PUBLIC_URL}/webhooks/uazapi`,\n  events: ['messages', 'messages_update', 'connection']\n});\n\nconst conn = await uazapi.instance.connect({\n  token: created.token,\n  serverId: created.serverId\n});\n// conn.instance.qrcode is a base64 PNG -> show to the user\n\n// Send a message later\nawait uazapi.messaging.sendText({\n  token: created.token,\n  serverId: created.serverId,\n  number: '5511999999999',\n  text: 'Olá!'\n});\n```\n\nSelection strategies: `pinned`, `round-robin`, `weighted-round-robin`,\n`least-loaded`, `fill-first`, or a custom function. The pool can be\nreconfigured at runtime — `pool.add()`, `pool.update(id, patch)`,\n`pool.disable(id)`, `pool.enable(id)`, `pool.remove(id)`, `pool.stats()`.\n\nSee [docs/providers.md](../../docs/providers.md) for the full reference.\n\n---\n\n## API Reference\n\n### Account Management\n\n```javascript\n// Generate OAuth link for user authentication\nconst authLink = await provider.account.getHostedAuthLink({\n  providers: ['LINKEDIN', 'WHATSAPP'],\n  successRedirectUrl: 'https://your-app.com/success',\n  failureRedirectUrl: 'https://your-app.com/error',\n  notifyUrl: 'https://your-api.com/webhooks/account'\n});\nconsole.log('Auth URL:', authLink.url);\n\n// Connect LinkedIn with credentials (direct)\nconst account = await provider.account.connectLinkedin({\n  username: 'user@email.com',\n  password: 'password'\n});\n\n// Get account details\nconst account = await provider.account.getById('account_id');\n\n// Disconnect account\nawait provider.account.disconnect('account_id');\n```\n\n### Users\n\n```javascript\n// Get own profile\nconst myProfile = await provider.users.getOwnProfile('account_id');\n\n// Get user by ID\nconst user = await provider.users.getOne('account_id', 'user_id');\n\n// Get full profile with all LinkedIn sections\nconst fullProfile = await provider.users.getFullProfile('account_id', 'user_id');\n// Returns: experiences, education, skills, certifications, etc.\n\n// Search users\nconst results = await provider.users.search({\n  account_id: 'account_id',\n  keywords: 'software engineer',\n  limit: 25\n});\n\n// Send connection request (invite)\nawait provider.users.sendConnectionRequest({\n  account_id: 'account_id',\n  user_id: 'linkedin_user_id',\n  message: 'Hi! I would like to connect.' // Optional, max 300 chars\n});\n```\n\n### Connections\n\nSearch 1st degree connections (your network):\n\n```javascript\nconst connections = await provider.connections.search({\n  account_id: 'account_id',\n  keywords: 'developer',           // Optional\n  job_title: ['CTO', 'CEO'],       // Optional, can be string or array\n  industry: ['Technology'],        // Optional\n  location: 'Brazil',              // Optional\n  limit: 100,                      // Default: 100\n  cursor: 'next_page_cursor'       // For pagination\n});\n\nconsole.log('Connections found:', connections.items.length);\nconsole.log('Next page:', connections.cursor);\n```\n\n### LinkedIn Search\n\nAdvanced LinkedIn search (2nd/3rd degree):\n\n```javascript\nconst results = await provider.linkedin.search({\n  account_id: 'account_id',\n  api: 'classic',                  // 'classic' or 'sales_navigator'\n  category: 'people',              // 'people', 'companies', 'jobs'\n  keywords: 'marketing manager',\n  job_title: ['Marketing Manager', 'CMO'],\n  industry: ['Marketing and Advertising'],\n  location: 'urn:li:geo:106057199',  // LinkedIn location URN\n  company: ['urn:li:company:1234'],\n  network_distance: [2, 3],        // 2nd and 3rd degree\n  limit: 50\n});\n```\n\n### Search Parameters (Autocomplete)\n\nGet autocomplete suggestions for search forms:\n\n```javascript\n// Search locations\nconst locations = await provider.searchParams.locations({\n  account_id: 'account_id',\n  keywords: 'Sao Paulo',\n  limit: 20\n});\n// Returns: [{ id, name, country, ... }]\n\n// Search industries\nconst industries = await provider.searchParams.industries({\n  account_id: 'account_id',\n  keywords: 'Technology',\n  limit: 20\n});\n// Returns: [{ id, name, ... }]\n\n// Search job titles\nconst jobTitles = await provider.searchParams.jobTitles({\n  account_id: 'account_id',\n  keywords: 'Software',\n  limit: 20\n});\n// Returns: [{ id, name, ... }]\n\n// Search companies\nconst companies = await provider.searchParams.companies({\n  account_id: 'account_id',\n  keywords: 'Google',\n  limit: 20\n});\n// Returns: [{ id, name, ... }]\n```\n\n### Messaging\n\n```javascript\n// Get all chats\nconst chats = await provider.messaging.getChats({\n  account_id: 'account_id',\n  limit: 50,\n  cursor: 'pagination_cursor'\n});\n\n// Get single chat\nconst chat = await provider.messaging.getChat({\n  account_id: 'account_id',\n  chat_id: 'chat_id'\n});\n\n// Get messages from chat\nconst messages = await provider.messaging.getMessages({\n  account_id: 'account_id',\n  chat_id: 'chat_id',\n  limit: 50,\n  before_id: 'message_id'  // For pagination (older messages)\n});\n\n// Send text message\nawait provider.messaging.sendMessage({\n  account_id: 'account_id',\n  chat_id: 'chat_id',\n  text: 'Hello!'\n});\n\n// Send message with attachment\nawait provider.messaging.sendMessageWithAttachment({\n  account_id: 'account_id',\n  chat_id: 'chat_id',\n  text: 'Check this file',\n  attachments: [{\n    filename: 'document.pdf',\n    buffer: fileBuffer,\n    mimetype: 'application/pdf'\n  }]\n});\n\n// Get attachment from message\nconst attachment = await provider.messaging.getAttachment({\n  account_id: 'account_id',\n  message_id: 'message_id',\n  attachment_id: 'attachment_id'\n});\n// Returns: { data: Buffer, contentType, contentDisposition }\n\n// Get attendee (participant) info\nconst attendee = await provider.messaging.getAttendeeById('attendee_id');\n\n// Get attendee profile picture\nconst picture = await provider.messaging.getAttendeePicture('attendee_id');\n// Returns: { data: Buffer, contentType } or null\n\n// Get own profile from chats (WhatsApp, Instagram)\nconst ownProfile = await provider.messaging.getOwnProfileFromChats('account_id');\n```\n\n### Webhooks\n\nProgrammatic webhook management:\n\n```javascript\n// List all webhooks\nconst webhooks = await provider.webhooks.list();\n\n// Create webhook\nconst webhook = await provider.webhooks.create({\n  request_url: 'https://your-api.com/webhooks/unipile',\n  account_ids: ['account_1', 'account_2']  // Optional filter\n});\n\n// Find webhook by URL\nconst webhook = await provider.webhooks.findByUrl('https://your-api.com/webhooks/unipile');\n\n// Ensure webhook exists (create if not)\nconst webhook = await provider.webhooks.ensureWebhook({\n  request_url: 'https://your-api.com/webhooks/unipile'\n});\n\n// Add account to webhook filter\nawait provider.webhooks.addAccountToWebhook(\n  'https://your-api.com/webhooks/unipile',\n  'new_account_id',\n  'LINKEDIN'  // or 'WHATSAPP', 'INSTAGRAM', etc.\n);\n\n// Remove account from webhook\nawait provider.webhooks.removeAccountFromWebhook(\n  'https://your-api.com/webhooks/unipile',\n  'account_id',\n  'LINKEDIN'\n);\n\n// Get account IDs for webhook\nconst accountIds = await provider.webhooks.getAccountIds(\n  'https://your-api.com/webhooks/unipile',\n  'LINKEDIN'\n);\n\n// Delete webhook\nawait provider.webhooks.delete('webhook_id');\n```\n\n---\n\n## Events\n\nNormalized event types across all providers:\n\n```javascript\nconst { EventTypes, parseWebhook } = require('@brainhours/relay-core');\n\n// Available event types\nEventTypes.MESSAGE_RECEIVED     // Incoming message\nEventTypes.MESSAGE_SENT         // Outgoing message (sent by you)\nEventTypes.MESSAGE_DELIVERED    // Message was delivered\nEventTypes.MESSAGE_READ         // Message was read\nEventTypes.MESSAGE_EDITED       // Message was edited\nEventTypes.MESSAGE_DELETED      // Message was deleted\nEventTypes.MESSAGE_REACTION     // Reaction added to message\nEventTypes.RELATION_CREATED     // New connection/relation accepted\nEventTypes.RELATION_REMOVED     // Connection removed\nEventTypes.ACCOUNT_CONNECTED    // Account connected to Unipile\nEventTypes.ACCOUNT_DISCONNECTED // Account disconnected\nEventTypes.ACCOUNT_STATUS       // Account status changed\n\n// Parse incoming webhook\nconst event = parseWebhook('unipile', req.body);\n\n// Event structure\n{\n  type: 'message_received',\n  provider: 'unipile',\n  accountId: 'account_id',\n  chatId: 'chat_id',\n  messageId: 'message_id',\n  senderId: 'sender_id',\n  content: 'Message text',\n  timestamp: Date,\n  raw: { /* original payload */ }\n}\n```\n\n---\n\n## Queue Integration\n\nOptional Bull queue integration for async webhook processing:\n\n```javascript\nconst { createWebhookQueue, addWebhookJob } = require('@brainhours/relay-core/queue');\nconst Redis = require('ioredis');\n\n// Create queue\nconst redis = new Redis(process.env.REDIS_URL);\nconst queue = createWebhookQueue(redis, {\n  defaultJobOptions: {\n    removeOnComplete: 100,\n    removeOnFail: 50\n  }\n});\n\n// Add job to queue\nawait addWebhookJob(queue, event, {\n  priority: 1,\n  delay: 0\n});\n\n// Process jobs\nqueue.process('webhook', 5, async (job) => {\n  const { event } = job.data;\n\n  // Handle event...\n  console.log('Processing:', event.type);\n});\n```\n\n---\n\n## Error Handling\n\n```javascript\ntry {\n  await provider.messaging.sendMessage({\n    account_id: 'account_id',\n    chat_id: 'chat_id',\n    text: 'Hello!'\n  });\n} catch (error) {\n  if (error.response) {\n    // Unipile API error\n    console.error('API Error:', error.response.status);\n    console.error('Message:', error.response.data);\n  } else {\n    // Network or other error\n    console.error('Error:', error.message);\n  }\n}\n```\n\nCommon error codes:\n\n| Code | Meaning |\n|------|---------|\n| 400 | Bad request (invalid parameters) |\n| 401 | Unauthorized (invalid token) |\n| 403 | Forbidden (no permission) |\n| 404 | Not found (account/chat/message) |\n| 429 | Rate limited |\n| 500 | Internal server error |\n\n---\n\n## Module format and TypeScript\n\nThis package is **CommonJS only** — use `require()`, not `import`:\n\n```js\nconst { UnipileProvider, EventTypes, parseWebhook } = require('@brainhours/relay-core');\n```\n\n**Type declarations are not shipped yet.** TypeScript consumers can still use\nthe package, but without autocomplete or type checking — add a local\ndeclaration to silence the resolution error:\n\n```typescript\n// types/relay-core.d.ts\ndeclare module '@brainhours/relay-core';\n```\n\nThe source carries JSDoc on most of the public surface, so generating `.d.ts`\nis planned. Contributions welcome — see [CONTRIBUTING.md](../../CONTRIBUTING.md).\n\n---\n\n## Complete Example\n\n```javascript\nconst express = require('express');\nconst { UnipileProvider, parseWebhook, EventTypes } = require('@brainhours/relay-core');\nrequire('dotenv').config();\n\nconst app = express();\napp.use(express.json());\n\n// Initialize provider\nconst provider = new UnipileProvider({\n  dsn: process.env.UNIPILE_DSN,\n  accessToken: process.env.UNIPILE_ACCESS_TOKEN\n});\n\nif (!provider.isInitialized()) {\n  console.error('Failed to initialize:', provider.getError());\n  process.exit(1);\n}\n\n// Webhook endpoint\napp.post('/webhooks/unipile', async (req, res) => {\n  try {\n    const event = parseWebhook('unipile', req.body);\n\n    if (event.type === EventTypes.MESSAGE_RECEIVED) {\n      console.log(`New message from ${event.senderId}: ${event.content}`);\n\n      // Auto-reply example\n      await provider.messaging.sendMessage({\n        account_id: event.accountId,\n        chat_id: event.chatId,\n        text: 'Thanks for your message! We will respond shortly.'\n      });\n    }\n\n    res.status(200).send('OK');\n  } catch (error) {\n    console.error('Webhook error:', error);\n    res.status(500).send('Error');\n  }\n});\n\n// Start server\napp.listen(3000, () => {\n  console.log('Server running on port 3000');\n});\n```\n\n---\n\n## License\n\nMIT - Guilherme Goulart\n","readmeFilename":"README.md"}