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

# Webhooks

> Subscribe to webhook events, the payload and headers each delivery carries, how to verify its signature, and how to handle retries, duplicates and out-of-order events.

TheGuarantors offers real-time updates on the status of an applicant's application via webhooks as it progresses through our system. Webhooks allow you to receive instant notifications when important events occur in the [application lifecycle](https://docs.theguarantors.com/application-lifecycle.md), enabling you to keep your systems synchronized without polling our APIs.

In the sandbox, you control when events are sent by triggering them yourself. See [Triggering Events in Sandbox](https://docs.theguarantors.com/sandbox.md#triggering-events-in-sandbox).

## Setting Up Webhooks

To establish a webhook subscription, reach out to [developer-support@theguarantors.com](mailto:developer-support@theguarantors.com). Please provide the following information when requesting a webhook subscription:

* **URL**: The endpoint where you want to receive webhook events
* **Event Types**: The specific events you wish to subscribe to
* **Secret** (optional): A secret for webhook authentication. If you don't provide one, we'll generate one and share it with you.

## Available Event Types

The following event types are available for subscription. Each one has its own page in the API reference with its full payload.

### Invite Events

| Event Type | Description | Triggered When |
| - | - | - |
| `invite.status.canceled` | Invite Canceled | An invite is canceled, such as through `POST /v1/invites/:id/cancel` |
| `invite.updated` | Invite Updated | An invite is updated, such as through `PATCH /v1/invites/:id`. Changing only `metadata` doesn't send this event |

### Invite Recipient Events

| Event Type | Description | Triggered When |
| - | - | - |
| `invite_recipient.added` | Recipient Added | A recipient is added to an invite, such as through `POST /v1/invites/:id/invite-recipients` |
| `invite_recipient.updated` | Recipient Updated | An invite recipient is updated, such as through `PATCH /v1/invite-recipients/:id`. Changing only `metadata` doesn't send this event |
| `invite_recipient.removed` | Recipient Removed | A recipient is removed from an invite, such as through `DELETE /v1/invite-recipients/:id` |

### Applicant Events

| Event Type | Description | Triggered When |
| - | - | - |
| `applicant.status.started` | Applicant Started | An invite recipient opens their invite link and starts their questionnaire |
| `applicant.status.submitted` | Applicant Submitted | An applicant submits their portion of the application |

### Application Events

| Event Type | Description | Triggered When |
| - | - | - |
| `application.status.started` | Application Created | An application is created from an invite |
| `application.status.submitted` | Application Submitted | Every applicant on the application has submitted their portion of it |
| `application.status.approved` | Application Approved | An application is approved for bonding |
| `application.status.denied` | Application Denied | An application is denied |
| `application.status.pending` | Application Pending | TheGuarantors needs more information from an applicant after submission |
| `application.status.canceled` | Application Canceled | An application is canceled through `POST /v1/applications/:id/cancel` or by TheGuarantors |

### Bond Events

| Event Type | Description | Triggered When |
| - | - | - |
| `bond.status.issued` | Bond Issued | A bond is issued for an approved application |
| `bond.status.canceled` | Bond Canceled | An issued bond is canceled |

## Webhook Payload Structure

All webhook payloads follow a consistent structure:

```json theme={null}
{
  "event_id": "48215562",
  "event_type": "application.status.approved",
  "occurred_at": "2025-07-28T18:27:54.690Z",
  "data": {
    // Event-specific data (see examples below)
  }
}
```

### Payload Fields

* **`event_id`** (string): Identifier of the event that triggered this webhook. It stays the same when a delivery is retried. Treat it as an opaque string; its format varies by event type.
* **`event_type`** (string): The type of event that occurred
* **`occurred_at`** (string): When the event occurred, as a UTC timestamp in ISO 8601 format
* **`data`** (object): Event-specific payload containing the relevant entity data

## Event-Specific Payloads

### Invite Events

For invite events (`invite.status.canceled`, `invite.updated`), the payload includes the full invite:

```json theme={null}
{
  "event_id": "3f6c2a91-7d4e-4b8a-9c15-2e8f0a6b4d73",
  "event_type": "invite.updated",
  "occurred_at": "2025-07-28T18:27:54.690Z",
  "data": {
    "invite": {...}
  }
}
```

### Invite Recipient Events

For invite recipient events (`invite_recipient.added`, `invite_recipient.updated`, `invite_recipient.removed`), the payload includes:

```json theme={null}
{
  "event_id": "7a4e9c12-3b6d-4e85-a1f7-9d2c5b8e0f46",
  "event_type": "invite_recipient.added",
  "occurred_at": "2025-07-28T18:27:54.690Z",
  "data": {
    "invite_id": "...",
    "application_id": null,
    "invite_recipient": {...}
  }
}
```

### Applicant Status Events

For applicant-related events (`applicant.status.started`, `applicant.status.submitted`), the payload includes:

```json theme={null}
{
  "event_id": "48212985",
  "event_type": "applicant.status.submitted",
  "occurred_at": "2025-07-28T18:27:54.690Z",
  "data": {
    "application_id": "sd_xyz789",
    "invite_recipient_id": "ivre_abc123",
    "applicant": {...}
  }
}
```

### Application Status Events

For application-related events (`application.status.started`, `application.status.submitted`, `application.status.approved`, `application.status.denied`, `application.status.pending`, `application.status.canceled`), the payload includes:

```json theme={null}
{
  "event_id": "48215571",
  "event_type": "application.status.approved",
  "occurred_at": "2025-07-28T18:27:54.690Z",
  "data": {
    "application": {...}
  }
}
```

### Bond Status Events

For bond-related events (`bond.status.issued`, `bond.status.canceled`), the payload includes:

```json theme={null}
{
  "event_id": "48218816",
  "event_type": "bond.status.issued",
  "occurred_at": "2025-07-28T18:27:54.690Z",
  "data": {
    "bond": {...}
  }
}
```

## Webhook Delivery

When an event occurs, TheGuarantors sends a POST request to your configured webhook URL with:

* **Method**: POST
* **Content-Type**: `application/json`
* **Body**: JSON payload containing the event data
* **Headers**:
  * Content-Type
  * X-TG-Webhook-ID: identifies this webhook. It stays the same when a delivery is retried.
  * X-TG-Signature

## Webhook Signature

To ensure webhook requests are generated by TheGuarantors and haven't been tampered with, we include a signature in the X-TG-Signature header.

* **Header Format**: `t=<timestamp>,v1=<signature>`
  * **t**: Unix timestamp of when the request was sent.
  * **v1**: The hex-encoded HMAC-SHA256 signature.

**Verification Steps:**

1. Extract the timestamp and signature from the header.
   1. Split the header by `,` to get the `t` and `v1` elements.
   2. Split each element by `=` to get the values.
2. Prepare the signed\_payload string:
   1. Concatenate the timestamp, a period, and the raw JSON request body.
   2. Format: `<timestamp>.<raw_body>`
3. Calculate the HMAC:
   1. Compute an HMAC-SHA256 of the signed\_payload using your Webhook Secret as the key.
4. Compare Signatures:
   1. Compare your calculated signature with the v1 signature provided in the header.
   2. (Optional) Check that the timestamp is within your tolerance window (e.g., 5 minutes) to prevent replay attacks.

## Webhook Reliability

To build a reliable integration with our webhook system, your application must be prepared to handle real-world delivery scenarios such as retries, out-of-order events, and duplicate deliveries. The sections below describe how TheGuarantors delivers webhook events and highlight the safeguards your webhook handler should implement to ensure consistent, fault-tolerant processing.

### Event Ordering

TheGuarantors cannot guarantee that webhook events will be delivered in the order they were generated. Events may arrive out of sequence due to various factors, including network latency, our retry mechanisms, or internal processing delays. Therefore, your webhook handler must be built to process events in any order and should never assume sequential arrival, even for events related to the same application.

### Retry Logic

We automatically retry webhook delivery if your endpoint returns a non-2xx HTTP status code or fails to respond in a timely manner. We use exponential backoff for each retry to prevent overwhelming your system. After 5 failed delivery attempts, the webhook will be permanently marked as failed, and no further retries will be attempted.

### Deduplication

TheGuarantors does not perform webhook event deduplication; you are responsible for implementing this logic in your handler to ensure events are not processed multiple times. We advise storing the `event_id` of every processed webhook event. Before processing a new event, your handler should check if its `event_id` is already in your records. If the ID is found, the handler should acknowledge the webhook (return a 2xx status) but skip the processing step.
