Getting started

HookChat turns Instagram and Messenger DMs into signed webhooks and gives you two send routes back. This page takes you from install to a first reply: one key, one endpoint, one test event, one message.

Before you start#

  • A HookChat workspace with a linked Instagram or Messenger account. Sign in to the console to create one; Connect an Instagram account walks through the link step. The API calls a workspace a tenant; the two words mean the same thing.
  • Node 20 or newer for TypeScript, Python 3.10 or newer, or Go 1.26 or newer. The CLI needs nothing else.
  • Somewhere public to receive HTTPS requests. For local work a tunnel is fine; the gateway rejects private and loopback URLs.

1. Install#

Every SDK is one typed method per API operation plus a webhook verifier. Pick one, or install the CLI on its own; every step below shows all four.

TypeScript
npm install @hookchat/node
Python
pip install hookchat
Go
go get hookchat.dev/go@v0.5.0
CLI
curl -fsSL https://get.hookchat.dev/install.sh | bash
None of these needs an account: the CLI binaries and the Go modules come from HookChat's own hosts, the SDKs from npm and PyPI. Every binary and its checksum is on the downloads page.

The install script downloads the release binary for your OS and CPU from get.hookchat.dev, checks its SHA-256 and puts hookchat on your path. With a Go toolchain you can use go install hookchat.dev/cli/cmd/hookchat@v0.5.0 instead. Release binaries for every platform are on the downloads page.

Check the install
hookchat version

2. Sign in to the CLI#

The CLI stores a key, a base URL and a workspace in a named profile so later commands need no flags. Paste the key at the prompt, or pass it with --api-key. The command pings the gateway before it writes anything.

Shell
# Paste the key at the prompt, or pass --api-key. Add --base-url when you run your own gateway
hookchat auth login --profile docs --api-key "$HOOKCHAT_API_KEY" --base-url "$HOOKCHAT_BASE_URL" --tenant "$HOOKCHAT_TENANT"
Shell
hookchat auth status

Flags override environment variables (HOOKCHAT_API_KEY, HOOKCHAT_BASE_URL, HOOKCHAT_TENANT), which override the profile. auth status shows which source is active. The full command list is in the CLI reference.

3. Create an API key#

Keys are bound to your workspace and carry a scope. A hookchat_live_ key sends real messages; a hookchat_test_ key routes sends through a mock connection and writes rows that reads only show with include_test. Create your first key in the console, then create the rest from code. Scopes, rotation and revoking are on Manage API keys.

TypeScript
import { HookChat } from '@hookchat/node'

const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })

// The plaintext secret is returned exactly once. Store it now; no read path returns it again.
const created = await client.keys.create({ scope: 'test', label: 'docs' })
console.log('created', created.id, created.prefix)
The secret is shown once, at creation, and never again. Put it in a secret manager or an environment variable such as HOOKCHAT_API_KEY; never in source control.

4. Register a webhook endpoint#

An endpoint is a public HTTPS URL the gateway POSTs signed events to. Creating one gives it a signing secret (whs_), returned exactly once. Omit events to subscribe to every event type.

TypeScript
import { HookChat } from '@hookchat/node'

const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })

const { endpoint, signing_secret } = await client.webhooks.create({ url: process.env.WEBHOOK_URL! })
console.log('endpoint', endpoint.id, endpoint.status)
console.log('signing secret starts with', signing_secret.slice(0, 4))
Keep the signing secret next to your receiver as WEBHOOK_SECRET. The endpoint id is what you pass to every later webhook command. A private, loopback or plain HTTP URL is refused with invalid_request and the reason in detail.

5. Receive your first event#

Every delivery is HMAC-SHA256 signed over the exact bytes sent, with the signature in the HookChat-Signature header. The SDK verifier checks the signature and a 300 second timestamp tolerance, then returns the typed event. Verify the raw body; re-serialising the JSON changes the bytes.

TypeScript
import { createServer } from 'node:http'
import { verifyWebhook, HookChatSignatureError } from '@hookchat/node'

const secret = process.env.WEBHOOK_SECRET!

createServer((req, res) => {
  const chunks: Buffer[] = []
  req.on('data', (chunk: Buffer) => chunks.push(chunk))
  req.on('end', () => {
    const raw = Buffer.concat(chunks) // the exact bytes, never re-serialised
    try {
      const event = verifyWebhook(raw, req.headers, secret)
      console.log('verified', event.type, event.id)
      res.writeHead(200).end()
    } catch (error) {
      if (error instanceof HookChatSignatureError) {
        res.writeHead(401).end()
        return
      }
      throw error
    }
  })
}).listen(Number(process.env.PORT))

With the receiver running, send a test.event through the real signing and delivery path. The gateway returns a delivery id straight away and delivers asynchronously.

TypeScript
import { HookChat } from '@hookchat/node'

const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })

const { delivery_id } = await client.webhooks.test(process.env.WEBHOOK_ID!)
console.log('queued test delivery', delivery_id)

Your receiver logs verified test.event evt_test_.... The body it verified looks like this:

A test.event delivery
{
  "id": "evt_test_5f1c9a2b7d3e",
  "type": "test.event",
  "created": 1756900000,
  "data": {
    "test": {
      "message": "This is a HookChat test event. If your endpoint verified this request’s signature, it is configured correctly.",
      "tenant": "acme",
      "nonce": "5f1c9a2b7d3e"
    }
  }
}

6. Send your first reply#

A real inbound DM arrives as message.received with a conversation_id. Reply inside the 24 hour window that opens with the participant's last message. The conversation id is the whole address: there is no tenant parameter on the send routes.

TypeScript
import { HookChat } from '@hookchat/node'

const client = new HookChat({ apiKey: process.env.HOOKCHAT_API_KEY!, baseUrl: process.env.HOOKCHAT_BASE_URL! })

const { platform_message_id } = await client.messages.reply({
  conversation_id: process.env.CONVERSATION_ID!,
  text: 'Thanks for the message, on it now.',
})
console.log('sent', platform_message_id)
Outside the 24 hour window the reply route refuses with window_closed (409). From 24 hours to 7 days a named human can still follow up on the human-agent route; past 7 days nothing can be sent. See Reply within the window and Send as a human agent.

Next#