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

# Zapier

> Wire a bot to a Zap — code-based verification and the response_url pattern.

Zapier works differently from n8n and Make. It is worth knowing why before you build.

<Warning>
  **Zapier cannot reply in the same request.** A Catch Hook always returns Zapier's own fixed response — you cannot change the body. Every Zapier bot must answer through the [`response_url`](/sending-messages/response-url).
</Warning>

That one rule shapes the whole setup: confirm automatically, then POST the answer.

## Set up the trigger

<Steps>
  <Step title="Add a Catch Hook">
    Create a Zap with **Webhooks by Zapier** → **Catch Hook**. Copy the custom webhook URL.
  </Step>

  <Step title="Paste it into your bot">
    Add it as your bot's webhook URL.
  </Step>

  <Step title="Verify with the code">
    Press **Verify**. Zapier cannot echo the challenge, so we skip that check for `zapier.com` addresses — any `2xx` counts as reachable.

    Instead, the event carries a human-readable `verification_code` like `XXXX-XXXX`. Open the Zap's trigger data, find `verification_code`, and paste it into the wizard.

    <Note>
      The code lasts **30 minutes**. Dashes and capital letters do not matter. If it runs out, press **Verify** again for a new one.
    </Note>
  </Step>
</Steps>

We match the real hostname: `hooks.zapier.com` counts; look-alikes such as `zapier.com.example.org` or `notzapier.com` do not.

## Reply to a message

<Steps>
  <Step title="Filter to real messages">
    Add a **Filter** so the Zap only continues when `event` exactly matches `message.created`. Without it, verify and test events run your whole Zap.
  </Step>

  <Step title="Do the work">
    ChatGPT, a lookup, a spreadsheet row — whatever your bot does.
  </Step>

  <Step title="POST the reply">
    Add **Webhooks by Zapier** → **POST**:

    * **URL**: map `response_url` from the trigger
    * **Payload Type**: `JSON`
    * **Data**:

    | Field  | Value                                  |
    | ------ | -------------------------------------- |
    | `text` | Your reply text                        |
    | `mid`  | `reply-` plus the trigger's `event_id` |

    * **Unflatten**: `no`
    * **Headers**: none — the key is in the URL
  </Step>
</Steps>

<Warning>
  Set **Unflatten** to `no`. If it stays on `yes`, Zapier turns a key like `message.text` into a nested object, and your payload stops being valid Block Kit.
</Warning>

## Sending blocks from Zapier

Zapier's **Data** fields are flat key-value pairs. They cannot build a nested `blocks` array. You have two options.

### Use the shortcut

Most replies do not need nesting. This is a complete, valid body:

| Field  | Value                               |
| ------ | ----------------------------------- |
| `text` | `Your order **#4821** has shipped!` |
| `mid`  | `reply-{{event_id}}`                |

We turn it into a proper `text` block on our side.

### Use Code by Zapier

For buttons, forms, or files, add a **Code by Zapier** step and build the JSON yourself:

```javascript theme={null}
const body = {
  mid: `reply-${inputData.event_id}`,
  blocks: [
    { type: 'text', text: inputData.reply },
    {
      type: 'button_group',
      buttons: [
        { action_id: 'track', label: 'Track package', value: inputData.order_id }
      ]
    }
  ]
}

const res = await fetch(inputData.response_url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
})

output = await res.json()
```

Map `event_id`, `reply`, `order_id`, and `response_url` into the step's **Input Data**.

<Tip>
  Code by Zapier is also the only real way to upload a file from a Zap, because the upload needs `multipart/form-data`.
</Tip>

## Handle button clicks

Clicks arrive at the same Catch Hook with `event` set to `interaction.button_clicked`. Use a **Filter** or **Paths** to branch on it, then read `action_id`, `value`, and `message_metadata`.

Reply the same way as above — POST to that event's own `response_url`.

## Always set a mid

Zapier replays and retries more than most tools. A `mid` turns a repeat POST into an update instead of a second message:

| Field | Value                |
| ----- | -------------------- |
| `mid` | `reply-{{event_id}}` |

`event_id` stays the same across retries of one event, so this makes your Zap safe to retry — for free.

## Fixing problems

<AccordionGroup>
  <Accordion title="Verification never completes">
    Reaching your hook and the code are two separate checks. A `2xx` only proves we found it. You must also paste the `verification_code` from the trigger data into the wizard, within 30 minutes.
  </Accordion>

  <Accordion title="I cannot find verification_code">
    Pull a fresh sample: press **Verify**, then **Test trigger** in the Zap. The event has `event` set to `webhook.verify` and carries the code.
  </Accordion>

  <Accordion title="The Zap runs on test events">
    Add a Filter on `event` exactly matching `message.created`. Verify and test events both set `test` to `true`.
  </Accordion>

  <Accordion title="The reply never appears">
    Zapier cannot reply in the same request — returning from the trigger does nothing. You need a **Webhooks by Zapier → POST** action aimed at `response_url`.
  </Accordion>

  <Accordion title="410 RESPONSE_URL_EXPIRED">
    The Zap took more than 45 minutes, likely queued on a busy plan. For work that slow, switch to an [API key](/sending-messages/api-key), which never expires.
  </Accordion>

  <Accordion title="422 BLOCK_VALIDATION_FAILED">
    Usually **Unflatten** is on, so `blocks` arrived broken. Set it to `no`, or build the JSON in a Code step.
  </Accordion>
</AccordionGroup>
