> For the complete documentation index, see [llms.txt](https://help.cerby.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.cerby.com/developer-tools/cerby-webhooks/implement-a-webhook-receiver.md).

# Implement a webhook receiver

This article describes the key components you need as a developer to implement a webhook receiver that accepts Cerby event deliveries. The following sections are covered:

* [Envelope format](#envelope-format)
* [HTTP headers](#http-headers)
* [Signature verification](#signature-verification)
* [Event types](#event-types)
* [Automation failure codes](#automation-failure-codes)
* [Delivery semantics](#delivery-semantics)
* [Sending to a Slack Incoming Webhook](#sending-to-a-slack-incoming-webhook)
* [Current release limitations](#current-release-limitations)

For an overview of how webhooks work and how to create a webhook endpoint in Cerby, refer to [Explore webhook notifications](https://github.com/cerbyinc/help-center/tree/main/management/workspace-settings/webhooks/explore-webhook-notifications.md) and [Create a webhook](https://github.com/cerbyinc/help-center/tree/main/management/workspace-settings/webhooks/create-a-webhook.md).

## Envelope format

Every webhook request body is a JSON object with a fixed top-level structure. The `error` field is present only on failure events. It is **absent** (not null) on all other event types.

The following is an example of a success event delivery:

```json
{
    "envelope_version": "1.0",
    "event_id": "01977f2e-9c1a-7d3b-8f00-2b9a4c5d6e7f",
    "event_type": "account.updated",
    "occurred_at": "2026-06-09T20:12:01.342Z",
    "workspace_id": "9f8e7d6c-5b4a-4f3e-9d2c-1b0a99887766",
    "data": { "application": "Salesforce" },
    "trace_id": null,
    "correlation_id": null
}
```

The following is an example of a failure event delivery (`automation.failed`):

```json
{
    "envelope_version": "1.0",
    "event_id": "01977f2e-9c1a-7d3b-8f00-2b9a4c5d6e7f",
    "event_type": "automation.failed",
    "occurred_at": "2026-06-09T20:12:01.342Z",
    "workspace_id": "9f8e7d6c-5b4a-4f3e-9d2c-1b0a99887766",
    "data": {
        "automation_job_id": "b1c2d3e4-f5a6-7890-bcde-fa1234567890",
        "application": "Salesforce",
        "account_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "automation_link": "https://<workspace>.cerby.com/api/v1/jobs/b1c2d3e4-f5a6-7890-bcde-fa1234567890",
        "action": "password_rotation",
        "remediation": null
    },
    "error": {
        "code": "BadCredentials",
        "message": "The username or password stored in Cerby is incorrect",
        "user_action": "review_credentials"
    },
    "trace_id": null,
    "correlation_id": null
}
```

The following table describes each field in the JSON object:

| Field              | Type                       | Description                                                                                                                                                                 |
| ------------------ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `envelope_version` | string                     | Always `"1.0"` in this release. New fields may be added without a version bump.                                                                                             |
| `event_id`         | string (UUIDv7)            | Unique per event. Stable across all retry attempts for the same event. Use as the per-event idempotency key.                                                                |
| `event_type`       | string                     | The event type string. See [Event types](#event-types).                                                                                                                     |
| `occurred_at`      | string (RFC 3339, UTC, ms) | When Cerby recorded the event. Not the source-system time.                                                                                                                  |
| `workspace_id`     | string (UUID)              | The Cerby workspace where the event occurred.                                                                                                                               |
| `data`             | object                     | Event-specific payload. Fields vary by event type.                                                                                                                          |
| `error`            | object                     | Present only on failure events. Contains `{ "code": string, "message": string \| null, "user_action": string \| null }`. **Absent (not present) on all other event types.** |
| `trace_id`         | null                       | Reserved. Always null in this release.                                                                                                                                      |
| `correlation_id`   | null                       | Reserved. Always null in this release.                                                                                                                                      |

### Forward compatibility

Apply the following forward compatibility rules when processing deliveries:

* Ignore unknown fields. New top-level keys may appear without a version bump.
* Tolerate null on `trace_id`, `correlation_id`, `error.message`, and `error.user_action`.
* Do not assume field ordering.
* Treat unknown `error.code` and `error.user_action` values as opaque strings. Do not reject events because of an unrecognized value.

## HTTP headers

Every webhook request includes the following headers:

| Header                        | Description                                                                                                                                                          |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Cerby-Signature`           | `<algo>=<base64url(sig)>` where `<algo>` is `ed25519` or `hmac-sha256`. Repeated during a key rotation (new key first, old key second).                              |
| `X-Cerby-Signature-Key-Id`    | UUID of the signing key used. One header per signature row, in the same order as `X-Cerby-Signature`.                                                                |
| `X-Cerby-Signature-Timestamp` | RFC 3339 millisecond-precision UTC timestamp. The timestamp is part of the signed message. Reject requests where it is more than five minutes from the current time. |
| `X-Cerby-Delivery`            | UUID constant across all retry attempts for the same event and webhook. Use as the per-delivery idempotency key.                                                     |
| `X-Cerby-Attempt`             | Attempt number starting at 1, incremented on each retry, up to 6.                                                                                                    |
| `Idempotency-Key`             | Same value as `X-Cerby-Delivery`. Provided as a convenience alias.                                                                                                   |

## Signature verification

Verify the signature on every incoming request before processing the event. A request that fails verification must be rejected with a 4xx response.

### How the signature is constructed

Cerby signs the following byte sequence:

```
<timestamp_ms>.<raw_body_bytes>
```

Where `<timestamp_ms>` is the `X-Cerby-Signature-Timestamp` header converted to an integer number of milliseconds since the Unix epoch, `.` is a literal period, and `<raw_body_bytes>` is the unmodified request body as received. There is no JSON canonicalization.

### Verification algorithm

To verify an incoming request, complete the following steps:

1. Parse `X-Cerby-Signature-Timestamp` to milliseconds. Reject the request if `|now_ms - ts_ms|` exceeds 300,000 (five minutes).
2. Build the signed byte sequence: `b"{ts_ms}." + raw_body`.
3. For each `(X-Cerby-Signature, X-Cerby-Signature-Key-Id)` pair:
   * Skip rows whose algorithm prefix you do not handle.
   * Look up the public key or shared secret for that key ID.
   * Decode the signature value: strip the `ed25519=` or `hmac-sha256=` prefix, then base64url-decode. The value is unpadded. Re-pad to the next multiple of four before decoding (`==` for Ed25519, `=` for HMAC-SHA256). Do not hard-code `==` across both algorithms.
   * Verify. Accept on the first row that validates. Refer to [Key rotation](#key-rotation) for how this applies during a key change.
4. If no row validates, reject the request.

### Reference implementations

The following implementations are kept in sync with the CI test suite. Adapt the input plumbing to your framework before use.

{% hint style="danger" %}
**IMPORTANT:** The following mistakes each cause every valid delivery to fail silently, producing a blanket reject that looks identical to a real signature failure.

* **Use the raw request body, not a re-serialized object.** Parsing the JSON then calling `JSON.stringify(req.body)` (or equivalent) produces different bytes than Cerby signed. Capture the raw body before any parsing, for example using `express.raw()` in Node.js.
* **Split repeated `X-Cerby-Signature` headers.** During a key rotation, Cerby sends two signature rows. Many frameworks collapse repeated headers into a comma-separated string. Split the value and build separate `sigs[]` and `kids[]` arrays before iterating. A verifier that only checks the first row rejects all rotated deliveries.
* **Fail closed on parse errors.** Treat a missing header or malformed body as a verification reject and respond 4xx, not 5xx. Cerby treats 5xx as retryable and will redeliver indefinitely.
  {% endhint %}

#### Python (Ed25519)

The following is the CI-tested reference implementation in Python.

```python
import base64, json, sys
from nacl.signing import VerifyKey
from nacl.exceptions import BadSignatureError

SKEW_MS = 300_000

def rfc3339_to_ms(ts):
    import datetime
    t = datetime.datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=datetime.timezone.utc)
    return int(t.timestamp() * 1000)

d = json.load(open(sys.argv[1]))
ts = d["ts"]
body = base64.b64decode(d["body_b64"])
public_keys = {k: base64.b64decode(v) for k, v in d["pubkeys"].items()}
now_ms = d["now_ms"]

ms = rfc3339_to_ms(ts)
if abs(now_ms - ms) > SKEW_MS:
    sys.exit(1)
signed_bytes = f"{ms}.".encode() + body
for sig, kid in zip(d["sigs"], d["kids"]):
    if not sig or not sig.startswith("ed25519="):
        continue
    pk = public_keys.get(kid)
    if not pk:
        continue
    try:
        VerifyKey(pk).verify(signed_bytes, base64.urlsafe_b64decode(sig[len("ed25519="):] + "=="))
        print("ACCEPT")
        sys.exit(0)
    except BadSignatureError:
        continue
sys.exit(1)
```

#### Node.js (Ed25519, Node 16+)

The following is the CI-tested reference implementation for Node.js 16 and later.

```javascript
const crypto = require("crypto");
const fs = require("fs");

const SKEW_MS = 300000;

function rfc3339ToMs(ts) {
  return Date.parse(ts);
}

const d = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
const ts = d.ts;
const body = Buffer.from(d.body_b64, "base64");
const pubkeys = {};
for (const k of Object.keys(d.pubkeys)) {
  pubkeys[k] = Buffer.from(d.pubkeys[k], "base64");
}
const nowMs = d.now_ms;

const ms = rfc3339ToMs(ts);
if (Number.isNaN(ms) || Math.abs(nowMs - ms) > SKEW_MS) {
  process.exit(1);
}
const signedBytes = Buffer.concat([Buffer.from(String(ms) + "."), body]);

for (let i = 0; i < d.sigs.length; i++) {
  const sig = d.sigs[i];
  const kid = d.kids[i];
  if (!sig || !sig.startsWith("ed25519=")) {
    continue;
  }
  const rawPub = pubkeys[kid];
  if (!rawPub) {
    continue;
  }
  try {
    const sigBytes = Buffer.from(sig.slice("ed25519=".length), "base64url");
    const key = crypto.createPublicKey({
      key: { kty: "OKP", crv: "Ed25519", x: rawPub.toString("base64url") },
      format: "jwk",
    });
    if (crypto.verify(null, signedBytes, key, sigBytes)) {
      console.log("ACCEPT");
      process.exit(0);
    }
  } catch {
    continue;
  }
}
process.exit(1);
```

### Key rotation

Retrieve your public key from `GET /v1/event-webhooks/{webhook_id}/keys`. During a rotation, this endpoint returns both the primary and secondary keys. Key your lookup by `X-Cerby-Signature-Key-Id` so each row resolves to the correct key.

When Cerby rotates a key, it dual-signs every request for 24 hours with both the old key (secondary) and the new key (primary), new key first. Accept the request if either row validates. After 24 hours, the old key expires and only the new key is used.

**Cache-miss as the rotation signal.** If an incoming request carries a `X-Cerby-Signature-Key-Id` you do not recognize, treat that as a signal that a rotation has started: re-fetch both public keys from the `/keys` endpoint, update your local lookup, then verify. Do not reject on an unknown key ID before refreshing.

Retries of a delivery that started before a rotation and complete after it arrive signed under the new key. Because you verify by key ID and accept on any valid row, this is transparent.

## Event types

This section lists the event types available for subscription, grouped by domain. Each subsection describes the `data` fields specific to that event type, as defined in the [Envelope format](#envelope-format).

### Account events

The following table lists account event types available for subscription:

| Event type                            | `data` fields                                         | Notes                                                                      |
| ------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------- |
| `account.created`                     | `account_id`, `domain`, `vault_id`, `requestor_id`    |                                                                            |
| `account.deleted`                     | `account_id`, `requestor_id`                          | **Not delivered in this release.**                                         |
| `account.disabled`                    | `account_id`, `requestor_id`                          |                                                                            |
| `account.enabled`                     | `account_id`, `requestor_id`                          |                                                                            |
| `account.credentials.updated`         | `account_id`, `application`, `credentials_identifier` | `credentials_identifier` is the username or email (PII).                   |
| `account.credentials.rotated`         | `account_id`, `application`, `credentials_identifier` | Rotation-sourced credential change.                                        |
| `account.credentials.rotation_failed` | `account_id`, `application`, `automation_link`        | Carries an `error` object. Provisional.                                    |
| `account.access.shared`               | `account_id`, `account_name`, `new_teams_ids`, `role` | `account_name` is PII. May co-fire with `account.access.shared_with_user`. |
| `account.access.shared_with_user`     | `account_id`, `account_name`, `new_users_ids`, `role` | May co-fire with `account.access.shared`.                                  |
| `account.login.failed`                | `account_id`, `application`, `automation_link`        | Carries an `error` object. Provisional.                                    |
| `account.mfa.enabled`                 | `account_id`, `application`                           | Provisional.                                                               |
| `account.mfa.disabled`                | `account_id`, `provider`                              |                                                                            |
| `account.mfa.setup_failed`            | `account_id`, `application`, `automation_link`        | Carries an `error` object. Provisional.                                    |

### Automation lifecycle events

All automation events include the base `data` fields: `automation_job_id`, `application`, `account_id`, `automation_link`. The following table lists automation lifecycle event types:

| Event type                              | Extra `data` fields                  | Notes                                                                                                          |
| --------------------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `automation.users.provisioned`          | `child_count`, `child_failure_count` | Rollup: one event per execution group.                                                                         |
| `automation.users.deprovisioned`        | `child_count`, `child_failure_count` | Rollup.                                                                                                        |
| `automation.users.role_updated`         | `child_count`, `child_failure_count` | Rollup.                                                                                                        |
| `automation.password.rotated`           | —                                    |                                                                                                                |
| `automation.mfa.enabled`                | —                                    |                                                                                                                |
| `automation.universal_logout.completed` | —                                    | Provisional.                                                                                                   |
| `automation.succeeded`                  | `action`                             | Always-fire catch-all. Co-fires alongside the matching specific `automation.*` success event for the same job. |
| `automation.failed`                     | `action`, `remediation`              | Carries an `error` object. See [Automation failure codes](#automation-failure-codes).                          |

{% hint style="info" %}
**NOTE:** `automation_link` is the public API URL for the source automation job (`GET https://<workspace>.cerby.com/api/v1/jobs/<automation_job_id>`). Fetching it requires an `X-API-Key` with the **Read automated jobs** (`read:automations`) scope. Two preconditions apply: the workspace must have public API access enabled, and the key's user must have visibility of the job's target account. A key user without account visibility returns 401; a key missing the scope returns 403. It is not a browser link. To inspect the job interactively, use the Automation Dashboard in Cerby.
{% endhint %}

{% hint style="info" %}
**NOTE:** `child_count` and `child_failure_count` are present but null in this release for rollup events.
{% endhint %}

{% hint style="info" %}
**NOTE:** `action` is an opaque pass-through of the internal workflow code that identifies the automation type (for example, `password_rotation`, `provide_access`). Treat any value as an opaque string. Do not condition logic on specific values.
{% endhint %}

### Provisional event types

Event types marked **Provisional** are defined in the catalog but do not yet have a confirmed delivery emitter in all cases. They may not arrive in production. The catalog will be finalized before the GA release.

### Reserved event types

The following event types are reserved for a future release. Subscribing to them currently returns HTTP 400:

* `account.vault.assigned`
* `account.trusted_session.established`
* `automation.users.attributes_updated`
* `automation.users.synced`

## Automation failure codes

The `automation.failed` event type covers all automation failures. The specific failure is identified by `error.code`, not by the event type. Individual error codes are not separately subscribable.

The `error` object has three fields:

| Field         | Type           | Description                                                                                                   |
| ------------- | -------------- | ------------------------------------------------------------------------------------------------------------- |
| `code`        | string         | Stable machine identifier. Treat unrecognized values as opaque generic failures.                              |
| `message`     | string or null | Human-readable description. May be null for some codes. Do not rely on its exact wording.                     |
| `user_action` | string or null | Coarse remediation category (see table below). Null or unknown values should be treated as "review in Cerby". |

The following error codes are available in this release:

| `error.code`                         | Tier | `error.user_action`       | `data.remediation`                                  |
| ------------------------------------ | ---- | ------------------------- | --------------------------------------------------- |
| `NotEnoughSeats`                     | 0    | `add_licenses`            | `{ seats_required, seats_available, license_type }` |
| `SubscriptionDeactivated`            | 0    | `reactivate_subscription` | null                                                |
| `UnderpermissionedAccount`           | 0    | `grant_permissions`       | null                                                |
| `InvalidBusinessId`                  | 0    | `fix_business_id`         | null                                                |
| `BadCredentials`                     | 1    | `review_credentials`      | null                                                |
| `EmptyPasswordError`                 | 1    | `review_credentials`      | null                                                |
| `AccountVaultError`                  | 1    | `contact_support`         | null                                                |
| `EmailNotProvidedError`              | 2    | `update_contact_info`     | null                                                |
| `InvalidEmail`                       | 2    | `update_contact_info`     | null                                                |
| `PhoneNotProvidedError`              | 2    | `update_contact_info`     | null                                                |
| `PhoneNotManagedError`               | 2    | `configure_mfa`           | null                                                |
| `GeneralMFANotManagedError`          | 2    | `configure_mfa`           | null                                                |
| `MFANotManagedError`                 | 2    | `configure_mfa`           | null                                                |
| `MFAAlreadyEnable`                   | 2    | `configure_mfa`           | null                                                |
| `OtherMFAMethodEnabledError`         | 2    | `configure_mfa`           | null                                                |
| `VerificationMethodsNotManagedError` | 2    | `configure_mfa`           | null                                                |
| `SecureDeviceNotFoundInStorage`      | 3    | `reauthenticate`          | null                                                |

{% hint style="info" %}
**NOTE:** `MFAAlreadyEnable` is the exact wire value. The missing "d" is intentional. Do not correct it in code or documentation.
{% endhint %}

The following table describes each `user_action` value:

| `user_action`             | Meaning                                                  |
| ------------------------- | -------------------------------------------------------- |
| `review_credentials`      | Update the username or password stored in Cerby          |
| `update_contact_info`     | Add or correct an email or phone number in Cerby         |
| `configure_mfa`           | Set up or transfer MFA management to Cerby               |
| `reauthenticate`          | Re-establish the Cerby browser extension trusted session |
| `contact_support`         | Contact Cerby Support to resolve the issue               |
| `add_licenses`            | Purchase additional seats in the target application      |
| `reactivate_subscription` | Reactivate the target application subscription           |
| `grant_permissions`       | Grant the Cerby-managed account the required permissions |
| `fix_business_id`         | Correct the business or tenant ID configured in Cerby    |

**Tier 0 codes** (`add_licenses`, `reactivate_subscription`, `grant_permissions`, `fix_business_id`) represent failures that must be remediated outside Cerby before retrying the automation. Only `NotEnoughSeats` populates `data.remediation` with machine-readable detail in this release. All other codes deliver `data.remediation: null`.

## Delivery semantics

This section describes the guarantees your receiver can rely on when processing Cerby webhook deliveries: how to handle duplicate deliveries, how Cerby retries failed requests, and how your endpoint's response code determines what happens next. Understanding these semantics will help you build a receiver that fails safely instead of dropping events or exhausting the retry budget.

### At-least-once and deduplication

Cerby guarantees at-least-once delivery. Your receiver may receive the same event more than once: on retries, or in rare cases as replays. Deduplicate as follows:

* **Per delivery:** Use `X-Cerby-Delivery` (constant across retries) combined with `X-Cerby-Attempt` (rising). A retry is identified only by a repeated `X-Cerby-Delivery` with a higher `X-Cerby-Attempt`.
* **Per event:** Use `event_id` to collapse the same logical event across different webhooks or delivery channels.

Do not deduplicate on payload content, URL, or signature equality.

### Retry policy

The following table describes the retry policy parameters:

| Parameter        | Value                                                              |
| ---------------- | ------------------------------------------------------------------ |
| Max attempts     | 6 (1 initial + 5 retries)                                          |
| Initial interval | 60 seconds                                                         |
| Backoff          | ×4 per attempt (approximately 1 min, 4 min, 16 min, 64 min, 4.3 h) |
| Max interval     | 6 hours                                                            |
| Hard deadline    | 24 hours total                                                     |

### Response handling

The following table describes how Cerby handles each response code from your endpoint:

| Your endpoint returns                     | Cerby behavior                                                     |
| ----------------------------------------- | ------------------------------------------------------------------ |
| `2xx`                                     | Delivery complete.                                                 |
| `408`, `429`, or `503` with `Retry-After` | Retried. Cerby honors `Retry-After`, clamped to one hour.          |
| Any other `4xx`                           | Fail-fast. The delivery is dead-lettered immediately, not retried. |
| `5xx`, timeout, or TCP error              | Retried with standard backoff.                                     |

A `401` or `404` response fails fast and will not heal on retry. Fix the endpoint configuration and use Cerby's redelivery tooling to re-send if needed.

Respond `2xx` quickly and process the event asynchronously. Slow synchronous processing risks burning your retry budget.

## Sending to a Slack Incoming Webhook

When your endpoint URL is a Slack Incoming Webhook (`hooks.slack.com`), Cerby delivers a Slack Block Kit message instead of the standard JSON envelope. This format is required for successful Slack delivery.

The Block Kit message is a lossy subset of the full event payload. It includes the following fields:

* Event type
* Workspace ID
* Application (when present in `data`)
* Account ID (when present in `data`)
* `occurred_at` timestamp
* For failure events: `error.code`, `error.message`, and `error.user_action` (labeled as "Remediation")

Fields present in the standard envelope that are not included in the Block Kit message include `data.action`, `data.automation_link`, and all other `data` fields not listed above. HTTPS receivers receive the full payload including these fields.

Cerby colors the attachment sidebar based on severity: `#2EB67D` (green) for success events and `#D50200` (red) for failure events.

The following is an example Block Kit message for a success event:

```json
{
  "text": "account.credentials.rotated — Salesforce",
  "attachments": [
    {
      "color": "#2EB67D",
      "blocks": [
        { "type": "header", "text": { "type": "plain_text", "text": "account.credentials.rotated" } },
        { "type": "section", "fields": [
          { "type": "mrkdwn", "text": "*Workspace*\n9f8e7d6c-5b4a-4f3e-9d2c-1b0a99887766" },
          { "type": "mrkdwn", "text": "*Application*\nSalesforce" },
          { "type": "mrkdwn", "text": "*Account*\n3fa85f64-5717-4562-b3fc-2c963f66afa6" },
          { "type": "mrkdwn", "text": "*Occurred*\n2026-07-21T16:45:12.031Z" }
        ]}
      ]
    }
  ]
}
```

## Current release limitations

The following limitations apply to webhooks in this release:

* **`account.deleted` not delivered:** The event type is available for subscription but Cerby does not deliver it in this release.
* **Canada region not supported:** Webhooks are not available for Canada-resident workspaces (`ca-central-1`) in this release.
* **Standalone events:** Events are not linked to related events. `trace_id` and `correlation_id` are reserved and always null in this release.
* **Provisional event types:** Several event types are defined in the catalog but do not yet have confirmed delivery in all cases. These are marked as provisional in the [Event types](#event-types) section above.

## Related articles

**Feature guides:**

* [Explore webhook notifications](https://github.com/cerbyinc/help-center/tree/main/management/workspace-settings/webhooks/explore-webhook-notifications.md)
* [Create a webhook](https://github.com/cerbyinc/help-center/tree/main/management/workspace-settings/webhooks/create-a-webhook.md)

**Troubleshooting:**

* [Troubleshooting: Webhook deliveries failing](https://github.com/cerbyinc/help-center/tree/main/tips_and_troubleshooting/troubleshooting/webhooks/troubleshooting-webhook-deliveries-failing.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://help.cerby.com/developer-tools/cerby-webhooks/implement-a-webhook-receiver.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
