# Guide to Autosana Webhooks: Features and Best Practices

Autosana webhooks tell your systems when a batch of AI-driven test runs has finished. Autosana runs end-to-end flows against mobile and web app builds, and a webhook is how the pass or fail result reaches your CI pipeline, release dashboard or team chat as soon as the batch completes.

This guide covers how to add an Autosana webhook, what the payload contains, how to verify the signature, and what Autosana's documentation leaves out.

## What are Autosana webhooks?

An Autosana webhook is an HTTPS URL you add in Autosana's settings. When Autosana finishes a batch of flow runs, it sends an HTTP `POST` with a JSON summary of the results to that URL. There is one kind of notification, so there are no events to choose from.

Signing is optional. If you set a signing secret on the webhook, Autosana adds an `X-Autosana-Signature-256` header holding `sha256=` and the hex HMAC-SHA256 of the raw body. Without a secret, deliveries are unsigned.

## Autosana webhook features

| Feature | Details |
| --- | --- |
| Configuration | **Settings > Integrations > Webhooks** |
| Endpoint URL | HTTPS only |
| Notification | `batch.completed`, sent when a batch of flow runs completes |
| Signature header | `X-Autosana-Signature-256`, only when a signing secret is set |
| Scheme | HMAC-SHA256, lowercase hex, prefixed with `sha256=` |
| Signed content | The raw request body only |
| Secret | A signing secret you choose when adding the webhook |
| Testing | A **Test** button sends a test delivery |
| Source IPs | The `flow_runners.virtual_runners.web_browsers` IPs from Autosana's network allowlist |

Autosana doesn't document a success status code, a response timeout, a retry policy or a delivery ID.

## The batch.completed payload

Here is Autosana's example, with the build URL shortened:

```json
{
  "event": "batch.completed",
  "timestamp": "2025-01-31T12:34:56.789Z",
  "app": {
    "id": "com.example.myapp",
    "bundle_id": "com.example.myapp",
    "name": "My App",
    "platform": "android",
    "url": "https://.../my_app.apk"
  },
  "git": {
    "commit_sha": "a1b2c3d4e5f6g7h8i9j0",
    "branch": "main"
  },
  "summary": {
    "total_groups": 3,
    "passed_groups": 2,
    "failed_groups": 1
  },
  "run_groups": [
    {
      "name": "Login Suite",
      "status": "passed",
      "url": "https://autosana.ai/runs/groups/f1e2d3c4-b5a6-7890-abcd-ef1234567890",
      "runs": [
        {
          "name": "User can sign in with email",
          "status": "passed",
          "url": "https://autosana.ai/runs/flow/12345678-abcd-ef12-3456-7890abcdef12"
        }
      ]
    }
  ]
}

```

* `summary` gives the pass and fail counts across run groups, which is usually all a CI gate needs.
* `run_groups` lists each group with its `status`, a link to the results, and its individual `runs`.
* `git` is included only when the build has git metadata, for example when it was uploaded from CI with commit information. Use `git.commit_sha` to match the result to a pipeline.
* `app` identifies the build and its `platform`.

## Setting up Autosana webhooks

1. Go to **Settings > Integrations > Webhooks** in Autosana and click **Add Webhook**.
2. Enter your HTTPS endpoint URL.
3. Add a signing secret. Generate a long random value, for example with `openssl rand -hex 32`, and store it server-side as `AUTOSANA_WEBHOOK_SECRET`.
4. Use the **Test** button to send a test delivery.

If your endpoint sits behind a firewall, allowlist the `flow_runners.virtual_runners.web_browsers` IPs from Autosana's network allowlist. Results webhooks come from those IPs for every platform, including mobile. The **Test** button is the exception: it sends from Autosana's API, which isn't on that list.

If you use Hookdeck, create a source with the Autosana source type and paste the same signing secret into it, then use the Hookdeck source URL as the webhook URL. You must set a signing secret on the Autosana webhook. Without one, deliveries are unsigned and fail verification in Hookdeck.

## Securing Autosana webhooks

Read `X-Autosana-Signature-256`, check that it starts with `sha256=`, and take the rest. Compute the hex HMAC-SHA256 of the raw request body with your secret as the key, and compare the two in constant time. If you configured a secret, reject requests that don't carry the header.

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

function verifyAutosanaSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret || !signatureHeader.startsWith('sha256=')) return false;
  const received = Buffer.from(signatureHeader.slice('sha256='.length).toLowerCase(), 'utf8');
  const expected = Buffer.from(
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex'),
    'utf8'
  );
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/autosana', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyAutosanaSignature(req.body, req.get('x-autosana-signature-256'), process.env.AUTOSANA_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // event.summary.failed_groups > 0 means at least one run group failed.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hashlib
import hmac

def verify_autosana_signature(raw_body: bytes, signature_header, secret: str) -> bool:
    if not signature_header or not secret or not signature_header.startswith("sha256="):
        return False
    received = signature_header[len("sha256="):].lower()
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))

# In FastAPI: raw_body = await request.body()
# verify_autosana_signature(raw_body, request.headers.get("x-autosana-signature-256"),
#                           os.environ["AUTOSANA_WEBHOOK_SECRET"])

```

Autosana's own JavaScript sample passes the header and computed value straight to `crypto.timingSafeEqual`, which throws when their lengths differ. The length check above avoids that. Only the body is signed and there is no timestamp header, so there is no replay window to check.

> **Make Autosana webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) verifies the `X-Autosana-Signature-256` header on every delivery, keeps a record of each test batch result, and retries delivery to your CI or notification service if it is briefly unavailable.

## Autosana webhook limitations and pain points

### Unsigned unless you set a secret

**The Problem:** Anyone who finds your endpoint URL can post a fake "all passed" result.

**Why It Happens:** The signing secret is optional when you add the webhook, and without it Autosana sends no signature header.

**Workarounds:**

* Always set a signing secret, and treat a missing header as a failed check.
* If an existing webhook has no secret, replace it with one that has.

**How Hookdeck Can Help:** Hookdeck's Autosana source verifies the header on every request and marks unsigned ones as failed verification, so you can see when a webhook was set up without a secret.

### No documented retries

**The Problem:** A test result disappears if your endpoint is down when the batch finishes.

**Why It Happens:** Autosana documents no retry policy, timeout or success code, so you can't count on a failed delivery being sent again.

**Workarounds:**

* Keep the receiver simple and highly available, and return `200` as soon as the request is verified.
* Link back to Autosana from your pipeline, so someone can check the run page if a result never arrives.

**How Hookdeck Can Help:** Hookdeck accepts the delivery as soon as it arrives and then delivers to your service with [retries](/docs/retries) you configure, so a short outage on your side doesn't lose the result.

### No delivery ID

**The Problem:** If the same result arrives twice, your pipeline acts on it twice.

**Why It Happens:** The payload has no event or delivery ID.

**Workarounds:**

* Build a key from fields in the body, such as `git.commit_sha` with `timestamp`, and skip repeats.

**How Hookdeck Can Help:** Hookdeck's [deduplication](/docs/deduplication) can drop repeats based on the body fields you choose.

## Best practices

### Verify against the raw body

Use `express.raw({ type: '*/*' })` in Express, `await request.text()` in Next.js and `await request.body()` in FastAPI. Parse only after the signature check passes.

### Gate on `summary`, link to `run_groups`

Use `summary.failed_groups` to decide pass or fail, and post the failing groups' `url` values where your team will see them.

### Send git metadata from CI

`git` appears only when the build carries git metadata. Upload builds from CI with commit information, so each result can be tied to a commit.

### Acknowledge fast and process asynchronously

Verify, store the result, return `200`, and update the pipeline or post notifications afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Autosana sends one `batch.completed` notification per finished batch of flow runs, signed with `X-Autosana-Signature-256` only when you set a signing secret. Set the secret, verify the `sha256=` hex HMAC against the raw body, use `summary` for your pass or fail decision, and plan for the absence of documented retries.

[Hookdeck Event Gateway](https://hookdeck.com) verifies every Autosana signature and holds each result until your service has handled it, so a brief outage doesn't drop a test run.

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