# Guide to VideoAsk Webhooks: Features and Best Practices

VideoAsk webhooks send each response to your own systems as soon as it's ready. When someone completes a videoask, or abandons it part-way, VideoAsk can post their answers, contact details and links to their video or audio to a URL you choose, and it can do the same when the transcription is finished or when you and a contact exchange messages. Teams use this to put leads into a CRM, start a hiring workflow, or store recordings and transcripts in their own systems.

This guide covers how to set up VideoAsk webhooks in the app or through the API, the events and payload shape, how to authenticate requests with a secret header, and the timing details that cause duplicate or late webhooks.

## What are VideoAsk webhooks?

A VideoAsk webhook belongs to one videoask (VideoAsk's API calls it a form). You choose a target URL and the events that trigger it, and VideoAsk sends a JSON `POST` each time one happens. Webhooks are available on paid plans.

You can create them in the videoask's **Connect** tab, set organisation-wide defaults that every new videoask inherits, or manage them with the Webhooks API, where each webhook is identified by a `tag`.

VideoAsk doesn't sign webhooks with a documented algorithm. Instead, both the Connect tab and the API let you add a custom header with a secret value, and VideoAsk sends that header with each request. VideoAsk's help article describes the header as "signed by VideoAsk", but what you configure is a static header name and value, and that value is what your receiver compares.

## VideoAsk webhook features

| Feature | Details |
| --- | --- |
| Availability | Paid plans |
| Configuration | **Connect** tab > **Webhooks**, organisation default webhook settings, or `PUT /forms/{form_id}/webhooks/{tag}` |
| Method and format | `POST`, JSON |
| Event field | `event_type` |
| Authentication | A secret header you set (**Add a secret**, or `headers` in the API). No signature |
| Success | Any `2xx` |
| Retries | 3, after 5, 10 and 20 minutes |
| Idle deadline | 60 minutes by default, 15 to 240 configurable |
| Testing | **View deliveries** > **Send test response** |
| Source IPs | Not documented |

## Common events

| Event | Sent when |
| --- | --- |
| `form_response` | A videoask is completed or abandoned and all video and audio is transcoded |
| `form_response_transcribed` | The same, once all media is also transcribed, which can take 3 to 4 minutes |
| `form_author_message` | You, the videoask's creator, send a message to a contact |
| `form_contact_message` | A contact sends a message |
| `form_contact_tagged` | A tag is added to a contact |
| `form_contact_untagged` | A tag is removed from a contact |

The API documentation publishes examples for the first four. The two tag events appear only in the help centre's list of toggles, so capture one before you build on it.

A `form_response` body, trimmed:

```json
{
  "event_id": "7df19833701662b6414ed1765dcc626c",
  "event_type": "form_response",
  "interaction_id": "386bbc0b-bb60-4b62-82fa-d630d0d2b83b",
  "contact": {
    "contact_id": "7c6550ff-fe15-424a-918e-2c1936db8aed",
    "name": "John Doe",
    "email": "john.doe@example.com",
    "status": "completed",
    "answers": [
      {
        "answer_id": "0f4ab93a-4914-4e4a-a33b-8466fda6147a",
        "question_id": "4d73e927-e0ef-4b73-a5da-cdda038545e1",
        "type": "poll",
        "poll_option_content": "Grapes"
      },
      {
        "answer_id": "995948a9-8a02-48f8-a018-769f0f25e7f8",
        "question_id": "a74ac70a-b207-492d-a3c8-43c8508f5c7a",
        "type": "video",
        "media_url": "https://media.videoask.com/transcoded/a8v1jwbdwwc7uq0keni9kf0c5l42vt42gp23f4ej.mp4",
        "share_url": "https://www.videoask.com/a8v1jwbdwwc7uq0keni9kf0c5l42vt42gp23f4ej",
        "transcode_status": "completed"
      }
    ]
  },
  "form": {
    "form_id": "5a1b8066-f92c-47bf-b8f2-3beb0a738122",
    "title": "Example form for documentation",
    "questions": ["..."]
  }
}

```

`form_response_transcribed` has the same shape and adds a `transcription` to each recorded answer. The message events add the message itself. The `form` object repeats the videoask's questions, so join answers to questions on `question_id`.

> **Inspect VideoAsk webhooks as they arrive.** Point a webhook at a [Hookdeck Inspect](https://inspect.hookdeck.com) URL and click **Send test response** to see the full body and your secret header, with no account or setup required.

## Setting up VideoAsk webhooks

Pick a header name, such as `Authorization` or `X-VideoAsk-Secret`, and generate a long random value, for example with `openssl rand -hex 32`.

In the app:

1. Open the videoask and click **Connect**.
2. Select **Webhooks**, then click **Connect**.
3. Enter the **Target URL**.
4. Toggle on the events you want.
5. Click **Add a secret** and enter the **Header name** and **Value**.
6. Adjust the idle deadline if you need to, then click **Save**.
7. Click **View deliveries**, then **Send test response**.

To apply the same webhook to every new videoask, set it up once under **Account & Org settings** > **Overview** > **Webhooks**. Existing videoasks keep their own settings.

With the API, create or update a webhook under a tag you choose:

```bash
curl --request PUT \
  --url "https://api.videoask.com/forms/$FORM_ID/webhooks/crm-sync" \
  --header "Authorization: Bearer $VIDEOASK_ACCESS_TOKEN" \
  --header "organization-id: $VIDEOASK_ORGANIZATION_ID" \
  --header 'Content-Type: application/json' \
  --data '{
    "url": "https://example.com/webhooks/videoask",
    "event_types": ["form_response", "form_response_transcribed"],
    "headers": { "X-VideoAsk-Secret": "YOUR_LONG_RANDOM_VALUE" }
  }'

```

Webhooks made in the app have a random tag, which `GET /forms/{form_id}/webhooks` returns.

To route through Hookdeck, create a source with the **VideoAsk** type and use its URL as the target URL. Enter the header name in **API Key Header** and the exact value, including any `Bearer ` prefix, in **API Key**. Hookdeck finds the header case-insensitively, compares the whole value, and accepts `POST`.

## Securing VideoAsk webhooks

Look up your header and compare its whole value with the stored secret, in constant time.

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

const HEADER_NAME = (process.env.VIDEOASK_WEBHOOK_HEADER || 'x-videoask-secret').toLowerCase();
const EXPECTED = process.env.VIDEOASK_WEBHOOK_SECRET;

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on unequal lengths, so check first.
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/videoask', express.raw({ type: '*/*' }), (req, res) => {
  const received = req.get(HEADER_NAME);
  if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
    return res.sendStatus(401);
  }

  let event;
  try {
    event = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400);
  }

  // Store event.event_id, queue the work by event.event_type, and respond.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

HEADER_NAME = os.environ.get("VIDEOASK_WEBHOOK_HEADER", "X-VideoAsk-Secret")
EXPECTED = os.environ.get("VIDEOASK_WEBHOOK_SECRET", "")

def verify_videoask_request(headers) -> bool:
    if not EXPECTED:
        return False  # never accept when unconfigured
    received = headers.get(HEADER_NAME)  # most frameworks look headers up case-insensitively
    if not received:
        return False
    # Compare bytes: compare_digest raises TypeError on a non-ASCII str.
    return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))

```

A static header authenticates the sender, not the body. It cannot detect a modified payload, and with no timestamp a captured request can be replayed and still pass. Responses can contain names, email addresses and recordings, so serve the endpoint over HTTPS only, use a long random value, and change it in the Connect tab or through the API if it might have leaked.

> **Make VideoAsk webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks your secret header, accepts every response as it arrives, and keeps it until your CRM or hiring workflow has processed it.

## VideoAsk webhook limitations and pain points

### Three retries in 35 minutes

**The Problem:** An outage of more than about half an hour loses responses.

**Why It Happens:** VideoAsk retries a failed request 3 times, after 5, 10 and 20 minutes, and documents nothing after that.

**Workarounds:**

* Store the request and return a `2xx` before doing any work.
* Check **View deliveries** after an incident and fetch missed responses through the API.

**How Hookdeck Can Help:** Hookdeck stays up when your service doesn't, so VideoAsk's deliveries succeed. Hookdeck then [retries](/docs/retries) delivery to your service on your schedule, for as long as you need, and you can replay any response.

### Two webhooks for one interaction

**The Problem:** The same respondent creates two CRM records.

**Why It Happens:** A respondent who stays idle past the idle deadline has their answers submitted. If they then finish, VideoAsk sends another webhook with the full response. Subscribing to both `form_response` and `form_response_transcribed` also gives you two webhooks per interaction by design.

**Workarounds:**

* Deduplicate on `interaction_id` for business logic, and on `event_id` for retries.
* Subscribe only to `form_response_transcribed` if you need transcripts and can wait a few minutes.

**How Hookdeck Can Help:** [Deduplication](/docs/deduplication) can discard repeats before they reach your service, and [filters](/docs/filters) can drop the event type you don't need.

### Media links expire

**The Problem:** Video links stored from a webhook stop working after a week.

**Why It Happens:** VideoAsk says the transcoded media URL expires after around a week. The interaction's share URL does not.

**Workarounds:**

* Download recordings you need to keep when the webhook arrives.
* Store the share URL for viewing, and make sure interactions are shareable.

**How Hookdeck Can Help:** Hookdeck queues the webhook until your service is ready, so the download job always starts from a fresh payload rather than a link that may already be days old.

### Unclear security wording

**The Problem:** You look for a signature to verify and can't find the algorithm.

**Why It Happens:** VideoAsk's help article talks about a header "signed by VideoAsk", but the setup is a header name and a static value, and no signing scheme is documented.

**Workarounds:**

* Treat the header as a shared secret and compare it whole.

**How Hookdeck Can Help:** Hookdeck's VideoAsk source compares the header you configure. Hookdeck answers VideoAsk with a `200` either way, but a request without the right header is recorded as failed verification and never reaches your service.

## Best practices

### Choose the event that matches your need

Use `form_response` for speed, or `form_response_transcribed` if you need transcripts. Subscribing to both doubles the work.

### Deduplicate on `event_id` and `interaction_id`

`event_id` catches retries; `interaction_id` catches idle-then-finished responses. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Join answers to questions

Match each answer's `question_id` against `form.questions` rather than relying on order.

### Set defaults for new videoasks

Organisation default webhook settings make sure new videoasks send webhooks, with the secret header, from day one.

### Acknowledge fast and process asynchronously

Store the response and return a `2xx`, then download media and update other systems. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

VideoAsk sends responses and messages as JSON `POST` requests, authenticated only by the custom header you add in the Connect tab or through the API. Compare that header in constant time, deduplicate on `event_id` and `interaction_id`, download media before the links expire, and respond quickly, because VideoAsk's three retries are over within 35 minutes.

[Hookdeck Event Gateway](https://hookdeck.com) checks the header, stores every response, and delivers it to your service with retries and replay.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle VideoAsk webhooks reliably in minutes.