# VideoAsk Webhooks

VideoAsk (the Typeform video-form product) sends a JSON `POST` when a respondent finishes a
videoask, a message is exchanged or a contact is tagged. Requests are **not signed**. The way to
authenticate them is a custom header you add to the webhook: in the videoask's **Connect** tab
(**Add a secret**: a header name and value), or through the API
(`PUT /forms/{form_id}/webhooks/{tag}` with a `headers` object). Your endpoint then checks that
header against the value you chose.

## When to Use This Skill

* How do I receive VideoAsk webhooks?
* Does VideoAsk sign webhook payloads? (No: use a secret header)
* How do I add a secret header to a VideoAsk webhook?
* How do I handle `form_response`, `form_response_transcribed`, `form_author_message`, `form_contact_message` or tag events?
* Why does my VideoAsk webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyVideoAskRequest(headers, {
  headerName = process.env.VIDEOASK_WEBHOOK_HEADER || 'secret',
  secret = process.env.VIDEOASK_WEBHOOK_SECRET,
} = {}) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[headerName.toLowerCase()]; // Node lowercases header names
  return Boolean(received) && safeEqual(received, secret); // compare the WHOLE value
}

```

* The header **name and value are yours**: you choose them under **Add a secret** in the Connect
  tab, or in the API's `headers` object (docs example: `{"secret": "open-sesame"}`). The name defaults to `secret`
  here; set `VIDEOASK_WEBHOOK_HEADER` if you used another. Look it up case-insensitively.
* `VIDEOASK_WEBHOOK_SECRET` is the exact, whole header value, including any `Bearer ` prefix you
  typed. Do not strip or parse a prefix; compare the whole string.
* Return `401` for a missing or mismatched header, `400` for a body that isn't a JSON object,
  `2xx` once handled (VideoAsk retries failures 3 times).
* Read the body raw (`express.raw({ type: '*/*' })`) and parse it only after the check passes.
* **A static shared credential authenticates the sender, not the body, and gives no replay
  protection** (no signature, no timestamp). Use HTTPS only, and rotate the credential (edit the
  webhook's secret in the Connect tab or through the API, then your env var) if it leaks.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/videoask-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/videoask-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/videoask-webhooks/examples/fastapi/).

## Event Types

The payload's `event_type` field carries the event name.

| Event | Sent When |
| --- | --- |
| `form_response` | A videoask is completed or abandoned and all media is transcoded |
| `form_response_transcribed` | Same, and media is also transcribed (can take 3-4 minutes) |
| `form_author_message` | The videoask creator sends a message to a contact (media transcoded and transcribed) |
| `form_contact_message` | A contact sends a message (media transcoded and transcribed) |
| `form_contact_tagged` | A tag is added to a contact |
| `form_contact_untagged` | A tag is removed from a contact |

The tag events come from the help centre's event list; the docs show no example payload for them.

## Environment Variables

```bash
VIDEOASK_WEBHOOK_HEADER=secret              # Header name you set in the webhook's headers object
VIDEOASK_WEBHOOK_SECRET=open-sesame         # Exact, whole header value

```

## Local Development

VideoAsk can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 videoask --path /webhooks/videoask

```

Use the printed URL as the webhook `url` when you call the VideoAsk API.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/videoask-webhooks/references/overview.md) - What VideoAsk webhooks are, events and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/videoask-webhooks/references/setup.md) - Create the webhook with a secret header via the API
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/videoask-webhooks/references/verification.md) - Header check details, limits, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/videoask-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests