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

# Destinations

> Configure email, Slack, webhook, and GitHub dispatch contact points for Netra automations, verify webhook signatures, and read the event payload format.

Automations deliver to the project's **contact points**, the same destinations [Alert Rules](https://docs.getnetra.ai/Alert-rules/Alert-rules.md) use. You create contact points once and reuse them across automations.

## Create a Contact Point

**1. Open Contact points**

Go to **Settings → Contact points** and click **Create Contact Point**.

**2. Name it and pick an integration**

Enter a **Name**, such as `Engineering Team Email`, and choose the **Integration**: Email, Slack, Webhook, or GitHub dispatch.

**3. Fill in the settings**

Enter the fields for the integration you chose. See the sections below.

**4. Save and send a test**

Save the contact point, then click the test action on its row. Netra sends a test event and shows whether the destination accepted it. You can send one test per contact point every 10 seconds.

> [!NOTE]
>
> Tokens and secrets are encrypted at rest. After you save, Netra shows only the last four characters.

## Email

| Field | Description |
| - | - |
| **Email Addresses** | One or more recipients |

Each event arrives as an email with the subject `[Netra] <event title>`, and a summary of the event details. Test run and red team events also include a link back to Netra.

## Slack

Choose one of two integration methods:

| Method | Fields |
| - | - |
| **Webhook URL** | A Slack incoming webhook URL, such as `https://hooks.slack.com/services/...` |
| **Slack API (Recipient + Token)** | A **Recipient** (`#channel` or `@username`) and a bot **Token** (`xoxb-...`) |

Each event arrives as a Slack message with a header and the event details. Test run and red team events also include a **View in Netra** link.

## Webhook

Netra sends a JSON `POST` to your HTTPS endpoint for every event.

| Field | Description |
| - | - |
| **URL** | Your HTTPS endpoint, such as `https://example.com/hooks/netra` |
| **Headers** | Optional custom headers, such as an `Authorization` header your endpoint expects |
| **Secret** | Optional HMAC signing secret. When set, every request is signed. |

Your endpoint must:

* Be reachable over the public internet. Private and internal addresses are rejected.
* Respond within 10 seconds. Redirects are not followed.
* Return a `2xx` status to mark the delivery as **Delivered**.

### Verify the Signature

When you set a **Secret**, each request includes an `x-netra-signature` header:

```
x-netra-signature: t=1792746942,v1=5f2b9c...
```

`v1` is the hex-encoded HMAC-SHA256 of `<t>.<raw request body>`, keyed with your secret. To verify a request, rebuild the HMAC over the raw body (before parsing JSON), compare it in constant time, and reject old timestamps to block replays.

```python Python theme={null}
import hashlib
import hmac
import time

TOLERANCE_SECONDS = 300


def verify_netra_signature(secret: str, raw_body: bytes, header: str) -> bool:
    parts = dict(item.split("=", 1) for item in header.split(","))
    timestamp = parts.get("t")
    signature = parts.get("v1")
    if not timestamp or not signature:
        return False

    if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False

    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
```

```typescript TypeScript theme={null}
import { createHmac, timingSafeEqual } from "crypto";

const TOLERANCE_SECONDS = 300;

export function verifyNetraSignature(secret: string, rawBody: string, header: string): boolean {
  const parts = Object.fromEntries(
    header.split(",").map((item) => item.split("=", 2) as [string, string])
  );
  const timestamp = parts["t"];
  const signature = parts["v1"];
  if (!timestamp || !signature) return false;

  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;

  const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex");
  return (
    expected.length === signature.length &&
    timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
  );
}
```

> [!WARNING]
>
> Compute the signature over the exact bytes you received. Parsing and re-serializing the JSON changes the body and the signature will not match.

## GitHub Dispatch

A GitHub dispatch contact point triggers a [`repository_dispatch`](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#repository_dispatch) event, so you can run a GitHub Actions workflow whenever an automation fires. For example, you can run regression tests when a new prompt version is published.

| Field | Description |
| - | - |
| **Dispatch URL** | `https://api.github.com/repos/<owner>/<repo>/dispatches` |
| **Event type** | The `event_type` your workflow listens for, such as `netra-event` |
| **GitHub token** | A token that can trigger workflows: a classic token with `repo` scope, or a fine-grained token with **Contents: write** on the repository |

The workflow receives the [event payload](#payload-format) in `github.event.client_payload`:

```yaml .github/workflows/netra.yml theme={null}
name: Netra automation

on:
  repository_dispatch:
    types: [netra-event]

jobs:
  handle:
    runs-on: ubuntu-latest
    steps:
      - name: Show event
        run: |
          echo "Type:   ${{ github.event.client_payload.type }}"
          echo "Action: ${{ github.event.client_payload.action }}"
          echo "Name:   ${{ github.event.client_payload.data.name }}"
```

> [!NOTE]
>
> GitHub limits `client_payload` to 64 KB. If an event is larger, Netra replaces the largest fields in `data` with a `[TRUNCATED: ...]` marker.

## Payload Format

Webhook and GitHub dispatch destinations receive the raw event. Email and Slack receive a formatted message built from the same data.

```json theme={null}
{
  "id": "7d0f3c2e-5a1b-4c8e-9f6d-2b1a0e9c4d71",
  "timestamp": "2026-10-23T09:15:42.120Z",
  "type": "prompt",
  "apiVersion": "v1",
  "action": "updated",
  "projectId": "3b9e1f4a-8c2d-4e6f-a1b3-5d7c9e0f2a4b",
  "sourceId": "c4a8e2f1-6b3d-4a9c-8e7f-1d2b3c4e5f6a",
  "data": {
    "id": "c4a8e2f1-6b3d-4a9c-8e7f-1d2b3c4e5f6a",
    "name": "support-agent-system",
    "promptType": "chat",
    "change": "label_attached",
    "labels": ["production"]
  }
}
```

| Field | Description |
| - | - |
| `id` | Unique event ID. Use it to deduplicate if your endpoint receives a retry. |
| `timestamp` | When the event happened (ISO 8601, UTC) |
| `type` | Source: `prompt`, `test_run`, or `redteam_run` |
| `apiVersion` | Payload version, currently `v1` |
| `action` | The event, such as `updated` or `evaluation_completed` |
| `projectId` | The Netra project the event belongs to |
| `sourceId` | ID of the prompt, test run, red team run, or red team config |
| `data` | Event details. See the table below. |

### Event Data by Source

| Source | `data` fields |
| - | - |
| **Prompt** | `id`, `name`, `promptType`, `change`, `labels`, and when relevant `version`, `versionId`, `previousVersion` |
| **Test run** (`test_run_triggered`) | `id`, `name`, `status`, `datasetId`, `datasetName`, `totalItems`, `totalEvaluators`, `url` |
| **Test run** (`test_run_failed`) | `id`, `name`, `status`, `datasetId`, `durationMs`, `url`, and when relevant `totalItems`, `completedItems`, `failureReason` |
| **Test run** (`evaluation_completed`) | `id`, `name`, `datasetId`, `datasetName`, `durationMs`, `url`, `totalEvaluators`, `passedEvaluators`, `failedEvaluators`, `totalItems`, `passedItems`, `failedItems`, `result` |
| **Red team run** (`redteam_run_completed`, `redteam_run_failed`) | `id`, `configId`, `configName`, `agentName`, `status`, `durationMs`, `completedTests`, `totalTests`, `failureReason`, `url` |

`url` links to the item in Netra.

In `evaluation_completed`, an evaluator counts as passed only if it passed on every item. `failedItems` counts every item that did not pass, including items that failed to run. `result` is a one-line summary, such as `4 of 5 evaluators passed, 18 of 20 items passed`.

### Test Events

The test action on a contact point sends an event with `type` set to `test` and `action` set to `ping`. Handle or ignore it in your endpoint.

***

## See Also

> [card] [Create an Automation](https://docs.getnetra.ai/Automations/creating-automations.md)
>
> Set up triggers, conditions, and destinations

> [card] [Alert Rules](https://docs.getnetra.ai/Alert-rules/Alert-rules.md)
>
> Get notified when metrics cross a threshold


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