# Webhooks

Hunty can deliver real-time event notifications to your server via webhooks. When a subscribed event occurs, Hunty sends an HTTP `POST` request to your configured endpoint with a JSON payload.

## Important

- **Full entity payload** — When an event fires, Hunty sends the complete state of the related entity (e.g., the full candidate object or the corresponding module). There is no partial delta: your handler always receives the latest snapshot.
- **Idempotency** — Every webhook call includes an `idempotencyId` field in the payload. Treat it as a unique request key and use it to deduplicate events on your end, preventing reprocessing if the same delivery is retried.
- **HTTP POST** — All webhook calls are `POST` requests to the URL configured per event. We strongly recommend protecting your endpoint with an API Key (see [Authentication & API Keys](/docs/authentication)).


## How It Works

1. You register a webhook endpoint URL for one or more events.
2. When an event fires, Hunty sends a `POST` request to your URL with the **full entity payload** wrapped in a versioned envelope (`WebhookTriggerEventV1DTO` or `WebhookTriggerEventV2DTO`).
3. Your endpoint must respond with an HTTP `2xx` status within the configured timeout (maximum: **60 seconds**).
4. If the request times out or returns a non-2xx response, Hunty retries the delivery using **exponential backoff** — use the `idempotencyId` to avoid reprocessing.


## Payload Structure

Hunty uses two message envelope versions. The `version` field indicates which schema was used.

### V1 (PascalCase fields)

```json
{
  "WebhookId": "wh_abc123",
  "IdempotencyId": "a3f2c1d4-5e6f-...",
  "Domain": "candidate",
  "Event": "CandidateStatusTransitionedEvent",
  "Version": 1,
  "Timestamp": 1713384000000,
  "Payload": { }
}
```

| Field | Type | Description |
|  --- | --- | --- |
| `WebhookId` | string | Unique identifier of the webhook subscription |
| `IdempotencyId` | string | Unique ID for this delivery — use as deduplication key |
| `Domain` | string | Entity domain (e.g., `candidate`) |
| `Event` | string | Event name (e.g., `CandidateStatusTransitionedEvent`) |
| `Version` | number | Always `1` for this schema |
| `Timestamp` | number | Unix timestamp in milliseconds of when the event fired |
| `Payload` | object | Full entity snapshot |


### V2 (camelCase fields)

```json
{
  "webhookId": "wh_abc123",
  "idempotencyId": "a3f2c1d4-5e6f-...",
  "domain": "candidate",
  "event": "CandidateStatusTransitionedEvent",
  "version": 2,
  "timestamp": 1713384000000,
  "payload": { }
}
```

| Field | Type | Description |
|  --- | --- | --- |
| `webhookId` | string | Unique identifier of the webhook subscription |
| `idempotencyId` | string | Unique ID for this delivery — use as deduplication key |
| `domain` | string | Entity domain (e.g., `candidate`) |
| `event` | string | Event name (e.g., `CandidateStatusTransitionedEvent`) |
| `version` | number | Always `2` for this schema |
| `timestamp` | number | Unix timestamp in milliseconds of when the event fired |
| `payload` | object | Full entity snapshot |


> The two versions are structurally identical — they only differ in field name casing. Check the `version` / `Version` field to determine which schema your subscription uses.


## Authentication

Each webhook subscription includes an authentication header that Hunty will send with every request, allowing you to verify the request originates from Hunty:

```http
<your-header-name>: <your-api-key>
```

You define both the header name and its value during the subscription request.

## Requesting a Webhook Subscription

To subscribe to one or more webhook events, send an email with the following details:

**To:** comite-seguridad@hunty.com
**CC:** santiago@hunty.com, enzo.garcia@hunty.com
**Subject:** `Solicitud Suscripción Webhook - <companyId>`

**Body must include one block per event subscription:**

- **Event(s):** The event name(s) you want to subscribe to (e.g., `CandidateStatusTransitionedEvent`)
- **Webhook URL:** Your endpoint that will receive the `POST` requests
- **Authentication header name:** The header Hunty should use to authenticate requests (e.g., `x-auth-api-key`)
- **API Key:** The value Hunty should send in that header
- **Timeout** *(optional)*: Maximum seconds Hunty should wait for your endpoint to respond before retrying. Maximum allowed value: **60 seconds**. Defaults to **60 seconds**.


**Example email body:**

```
Hi,

We would like to subscribe to the following webhook event:

Event: CandidateStatusTransitionedEvent
Webhook URL: https://your-domain.com/webhooks/hunty
Authentication header: x-auth-api-key
API Key: your_secret_key
Timeout: 30

Thank you.
```

## Retry Policy

If a webhook delivery fails (timeout or non-2xx response), Hunty retries automatically using **exponential backoff**:

| Parameter | Value |
|  --- | --- |
| Minimum backoff duration | 10 seconds |
| Maximum backoff duration | 600 seconds |


Each retry interval grows exponentially from 10 s up to the 600 s cap. Because retries can occur, always use the `idempotencyId` to deduplicate deliveries on your end.

## Automatic Failure Handling

Hunty monitors the health of each webhook endpoint. If your endpoint fails to respond successfully for an extended period, Hunty will automatically stop delivering events to it.

### How it works

- Each webhook has a **grace period** (default: **6 hours**) that starts counting from the first consecutive failure.
- Every failed delivery (timeout or non-2xx response) increments the failure counter and records the timestamp of the first failure.
- A **single successful delivery** (2xx response) resets the failure counter and the grace period timer back to zero.
- If failures continue for the entire duration of the grace period without a single successful delivery, Hunty **automatically stops** the webhook — no further events will be delivered to that endpoint.


### What happens when a webhook is stopped

- **Deliveries stop immediately** — Hunty will not attempt to send any new events to your endpoint.
- **Events are not replayed** — Any events that occur while the webhook is stopped are **permanently lost** for that subscription. Hunty does not queue or replay missed events once the webhook is reactivated.
- **Other webhooks are not affected** — If you have multiple webhook subscriptions, only the failing one is stopped.


### Reactivating a stopped webhook

To reactivate a stopped webhook, contact the Hunty team. Once reactivated:

- The failure counter and grace period timer reset to zero.
- Deliveries resume for **new events only** — events that occurred during the stopped period are not re-sent.


### Recommendations

- **Ensure high availability** — Your webhook endpoint should be highly available. Transient failures (e.g., a brief server restart) will not stop your webhook as long as they resolve within the 6-hour grace period.
- **Return 2xx promptly** — Respond with a `2xx` status as quickly as possible, even if you process the event asynchronously. This prevents timeouts from counting as failures.
- **Monitor your integration** — If you stop receiving webhook events unexpectedly, your endpoint may have been automatically stopped due to repeated failures. Contact the Hunty team to verify the status and reactivate if needed.