Quickstart

Three steps: install the SDK, verify a webhook delivery, send a reply. All examples use @hookchat/node, the official TypeScript SDK.

1. Install and construct the client

# Distribution is internal (git) for now: there is no npm publish yet.
npm install @hookchat/node
import { HookChat } from '@hookchat/node'

const hookchat = new HookChat({
  apiKey: process.env.HOOKCHAT_API_KEY,           // hookchat_live_… / hookchat_test_…
  baseUrl: 'https://hookchat.dev', // staging: https://stg.hookchat.dev
})

Create an API key in the console; the secret is shown exactly once. Register a webhook endpoint there too: its signing secret (whs_…) is also shown exactly once.

2. Receive and verify an event

Every delivery is HMAC-SHA256 signed over the exact transmitted bytes. Verify against the raw request body. Re-serialising parsed JSON changes the digest and fails intermittently. verifyWebhook checks the signature, enforces a timestamp tolerance of about 300 seconds, and returns the parsed, typed event.

import { verifyWebhook, HookChatSignatureError } from '@hookchat/node'

app.post('/hook', async (req, res) => {
  const raw = await readRawBody(req) // the EXACT bytes: do not re-serialise
  try {
    const event = verifyWebhook(raw, req.headers, process.env.HOOKCHAT_WEBHOOK_SECRET)
    switch (event.type) {
      case 'message.received':
        // event.data.message.text, .conversation_id, .platform, …
        break
      case 'message.sent':
      case 'message.failed':
      case 'test.event':
      case 'account.connected':
      case 'account.disconnected':
        break
    }
    res.status(200).end()
  } catch (e) {
    if (e instanceof HookChatSignatureError) return res.status(401).end()
    throw e
  }
})
Delivery is at-least-once and unordered. Dedupe on the HookChat-Event-Id header, and sort by the envelope created time where order matters. See Webhooks and events.

3. Send a reply

Reply inside the conversation's 24-hour window with one call. The conversation id is the only target you need: there is no ?tenant on the send routes.

import { HookChatError } from '@hookchat/node'

try {
  const { platform_message_id } = await hookchat.messages.reply({
    conversation_id: event.data.message.conversation_id,
    text: 'Thanks for the message, on it now.',
  })
} catch (e) {
  if (e instanceof HookChatError) {
    // Branch on the stable code, never the message.
    if (e.code === 'window_closed') {
      // Out of the 24h window: offer the human-agent path instead.
    }
  }
}

On success the promise resolves with { platform_message_id }. On a policy refusal it rejects with a HookChatError carrying a stable code. The full send model and every error code are on the Sending messages page.