> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meetdarwin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Send handoffs to Slack

> Post to a Slack channel within seconds when a visitor asks the Darwin chat for a person: a complete webhook receiver in Node with signature checks and de-duplication.

When a visitor asks to talk to a person, post it to a Slack channel within seconds, with the page they were on and a link to the conversation.

**You need:** a Slack [incoming webhook URL](https://api.slack.com/messaging/webhooks) for the channel, and somewhere to run a small HTTPS function (a serverless function, a container or an existing server).

```javascript server.mjs theme={null}
import crypto from "node:crypto";
import http from "node:http";

const DARWIN_SECRET = process.env.DARWIN_WEBHOOK_SECRET; // whsec_... from Integrations → API
const SLACK_URL = process.env.SLACK_WEBHOOK_URL;
const seen = new Set(); // use a database or cache in production

function verify(headers, body) {
  const id = headers["webhook-id"], ts = headers["webhook-timestamp"];
  if (!id || !ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const key = Buffer.from(DARWIN_SECRET.replace(/^whsec_/, ""), "base64");
  const expected = crypto.createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest("base64");
  return (headers["webhook-signature"] || "").split(" ").some((entry) => {
    const sig = entry.split(",")[1] || "";
    return sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  });
}

http
  .createServer((req, res) => {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", async () => {
      if (req.method !== "POST" || !verify(req.headers, body)) return res.writeHead(400).end();
      res.writeHead(200).end(); // answer Darwin first
      const event = JSON.parse(body);
      if (event.type !== "handoff.requested" || seen.has(event.id)) return;
      seen.add(event.id);
      const c = event.data.object;
      if (!c) return;
      const who = [c.lead.first_name, c.lead.last_name].filter(Boolean).join(" ") || c.lead.email || "A visitor";
      await fetch(SLACK_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          text: `:raising_hand: ${who} asked for a person on ${c.page_path ?? "your site"}\n>${c.handoff?.reason ?? c.preview ?? ""}\n<https://app.meetdarwin.ai/admin/conversations|Open Conversations in Darwin>`,
        }),
      });
    });
  })
  .listen(process.env.PORT || 3000);
```

**1. Deploy the function**

Deploy it so it is reachable at an HTTPS address, for example `https://hooks.example.com/darwin`. Set `SLACK_WEBHOOK_URL` now; you'll get `DARWIN_WEBHOOK_SECRET` in the next step.

**2. Add the endpoint in Darwin**

In **Integrations → API → Webhooks**, select **Add endpoint**, paste the address, choose only `handoff.requested`, and select **Add endpoint**. Copy the signing secret into `DARWIN_WEBHOOK_SECRET` and redeploy.

**3. Test it**

Select **Send test event** in the endpoint's menu. Darwin should report a `200`. The test event's type is `webhook.test`, so nothing is posted to Slack.

**It worked if**, after someone asks the chat on your live site to talk to a person, a message appears in the channel within a few seconds.

The same pattern sends `meeting.booked` to a sales channel or `lead.captured` to an SDR queue: subscribe to the type and read `data.object`. See [Webhooks](https://docs.meetdarwin.ai/api-reference/webhooks.md) for every payload.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.