# Webhooks

Receive real-time notifications about events during client interactions with Amigo.

{% hint style="info" %}
**Classic API.** These webhook endpoints are organization-scoped under the Classic API at `api.amigo.ai`.
{% endhint %}

{% hint style="info" %}
**Real-time events.** Webhooks let your application react immediately to conversation events, post-processing completions, and other system activities.
{% endhint %}

## Webhook Destinations

{% tabs %}
{% tab title="Configuration" %}

| Feature                  | Details                                        |
| ------------------------ | ---------------------------------------------- |
| **Maximum destinations** | 10 per organization                            |
| **Event filtering**      | Specify which event types to receive           |
| **Retry configuration**  | Customize retry attempts for failed deliveries |
| **Secret management**    | Unique secret per destination for security     |
| {% endtab %}             |                                                |

{% tab title="Capabilities" %}

* Real-time event notifications
* Selective event subscription
* Automatic retry with exponential backoff
* Signature verification for security
* Zero-downtime secret rotation
  {% endtab %}
  {% endtabs %}

#### Managing webhook destinations

Use these endpoints to manage your webhook destinations.

{% openapi src="<https://api.amigo.ai/v1/openapi.json>" path="/v1/{organization}/webhook\_destination/" method="post" %}
<https://api.amigo.ai/v1/openapi.json>
{% endopenapi %}

{% openapi src="<https://api.amigo.ai/v1/openapi.json>" path="/v1/{organization}/webhook\_destination/{webhook\_destination\_id}" method="delete" %}
<https://api.amigo.ai/v1/openapi.json>
{% endopenapi %}

{% openapi src="<https://api.amigo.ai/v1/openapi.json>" path="/v1/{organization}/webhook\_destination/" method="get" %}
<https://api.amigo.ai/v1/openapi.json>
{% endopenapi %}

{% openapi src="<https://api.amigo.ai/v1/openapi.json>" path="/v1/{organization}/webhook\_destination/{webhook\_destination\_id}" method="post" %}
<https://api.amigo.ai/v1/openapi.json>
{% endopenapi %}

{% hint style="info" %}
**URL cannot be changed.** The URL of a webhook destination cannot be updated. To change the URL, create a new webhook destination and delete the old one.
{% endhint %}

{% openapi src="<https://api.amigo.ai/v1/openapi.json>" path="/v1/{organization}/webhook\_destination/{webhook\_destination\_id}/rotate-secret" method="post" %}
<https://api.amigo.ai/v1/openapi.json>
{% endopenapi %}

{% hint style="info" %}
**Rate limit.** Secret rotation can occur at most once per hour for each webhook destination.
{% endhint %}

{% openapi src="<https://api.amigo.ai/v1/openapi.json>" path="/v1/{organization}/webhook\_destination/{webhook\_destination\_id}/delivery" method="get" %}
<https://api.amigo.ai/v1/openapi.json>
{% endopenapi %}

### Delivery and Retries

{% hint style="warning" %}
**Automatic retry logic.** Failed webhook deliveries are retried automatically with exponential backoff to ensure reliable event delivery.
{% endhint %}

| Retry Attempt | Delay | Total Time |
| ------------- | ----- | ---------- |
| Initial       | 0s    | Immediate  |
| Retry 1       | 5s    | 5s         |
| Retry 2       | 10s   | 15s        |
| Retry 3       | 20s   | 35s        |
| Retry 4       | 20s   | 55s        |

* Configure retry attempts via the `retry_attempts` parameter.
* View delivery history for the past 30 days via API.

#### Delivery sequence

{% @mermaid/diagram content="%%{init: {"theme": "base", "themeVariables": {"actorBkg": "#083241", "actorTextColor": "#FFFFFF", "actorBorder": "#083241", "signalColor": "#575452", "signalTextColor": "#100F0F", "labelBoxBkgColor": "#F1EAE7", "labelBoxBorderColor": "#D7D2D0", "labelTextColor": "#100F0F", "loopTextColor": "#100F0F", "noteBkgColor": "#F1EAE7", "noteBorderColor": "#D7D2D0", "noteTextColor": "#100F0F", "activationBkgColor": "#E8E2EB", "activationBorderColor": "#083241", "altSectionBkgColor": "#F1EAE7", "altSectionColor": "#100F0F"}}}%%
sequenceDiagram
autonumber
participant A as Amigo Events
participant W as Webhook Destination (Your Endpoint)

A->>W: POST /webhook<br/>{ event, headers, signature }
alt 2xx ack
W-->>A: 2xx Accepted/OK
else Non-2xx / timeout
W-->>A: 5xx/timeout
A->>W: Retry 1 (5s backoff)
W-->>A: 5xx/timeout
A->>W: Retry 2 (10s backoff)
A->>W: Retry 3 (20s backoff)
A->>W: Retry 4 (20s backoff)
end

Note over A,W: `x-amigo-idempotent-key` stays constant<br/>across retries for deduplication" %}

## Security

### Webhook signatures

Every webhook request is cryptographically signed so you can verify authenticity.

{% hint style="danger" %}
**Secret storage.** Store webhook secrets securely. Never commit them to version control or expose them in client-side code.
{% endhint %}

**Signature process:**

1. Receive the secret when creating a webhook destination.
2. Amigo signs all requests with HMAC-SHA256.
3. Validate the signature on receipt to verify authenticity.

### Request headers

| Header                      | Description                                                    |
| --------------------------- | -------------------------------------------------------------- |
| `x-amigo-idempotent-key`    | Unique identifier persisting through retries for deduplication |
| `x-amigo-request-timestamp` | UNIX timestamp (milliseconds) of the delivery attempt          |
| `x-amigo-request-signature` | HMAC-SHA256 signature of `v1:{timestamp}:{body}`               |

### Signature verification

{% tabs %}
{% tab title="Node.js Example" %}

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

function verifyWebhookSignature(body, timestamp, signature, secret) {
  // 1. Concatenate version, timestamp, and body
  const payload = `v1:${timestamp}:${body}`;
  
  // 2. Compute HMAC-SHA256
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  
  // 3. Compare signatures
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}
```

{% endtab %}

{% tab title="Python Example" %}

```python
import hmac
import hashlib

def verify_webhook_signature(body, timestamp, signature, secret):
    # 1. Concatenate version, timestamp, and body
    payload = f"v1:{timestamp}:{body}"
    
    # 2. Compute HMAC-SHA256
    expected_signature = hmac.new(
        secret.encode(),
        payload.encode(),
        hashlib.sha256
    ).hexdigest()
    
    # 3. Compare signatures
    return hmac.compare_digest(signature, expected_signature)
```

{% endtab %}
{% endtabs %}

### Secret rotation

{% hint style="success" %}
**Zero-downtime rotation.** A dual-signing period lets you rotate secrets smoothly without missing events.
{% endhint %}

**Rotation process:**

| Step | Action                     | Duration                      |
| ---- | -------------------------- | ----------------------------- |
| 1    | Call the rotation endpoint | Immediate                     |
| 2    | Receive the new secret     | Immediate                     |
| 3    | Dual-signing begins        | 30 minutes                    |
| 4    | Update verification logic  | During dual-signing           |
| 5    | Old secret expires         | After `dual_signing_stops_at` |

**During the dual-signing period:**

* Amigo sends two signatures per request.
* Accept either the old or the new signature.
* No webhook deliveries are lost.

#### Rotation states

{% @mermaid/diagram content="%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#D4E2E7", "primaryTextColor": "#100F0F", "primaryBorderColor": "#083241", "lineColor": "#575452", "textColor": "#100F0F"}}}%%
stateDiagram-v2
direction LR
OldActive: Old secret active
DualSigning: Dual-signing window
NewActive: New secret active

\[*] --> OldActive
OldActive --> DualSigning: rotate secret
DualSigning --> NewActive: dual\_signing\_stops\_at
NewActive --> \[*]

note right of DualSigning
Both old and new signatures<br/>are accepted during this window
end note" %}

## Webhook Event Types

### API key events

#### `api-key-expiration-soon`

Fires when an API key is approaching its expiration date. Notifications arrive at 14, 7, and 1 day before expiration, giving you time to rotate keys before service is disrupted.

**Payload structure:**

```javascript
{
    // The webhook event type
    type: "api-key-expiration-soon",

    // Your organization's unique identifier
    org_id: string,

    // The API key that is expiring
    api_key_id: string,

    // When the API key will expire (UTC datetime)
    expiration_time: string,

    // Unique key for deduplication across retries
    idempotent_key: string
}
```

{% hint style="success" %}
**Proactive key rotation.** Subscribe to this event to automate key rotation. Create a new API key, transition your applications, and revoke the expiring key before it becomes invalid.
{% endhint %}

### Post-processing events

#### `conversation-post-processing-complete`

Fires when asynchronous post-processing analysis completes for a conversation.

{% hint style="info" %}
**Asynchronous processing.** Post-processing runs after conversations end, performing deep analysis without blocking the user experience.
{% endhint %}

**Payload structure:**

```javascript
{
    // The webhook event type
    type: "conversation-post-processing-complete",

    // The specific post-processing operation that completed
    post_processing_type: "generate-tasks" | "generate-user-models" | "extract-memories" | "compute-metrics",

    // Unique identifier for the conversation that was analyzed
    conversation_id: string,

    // Your organization's unique identifier
    org_id: string
}
```

**Post-processing types:**

| Type                   | Description                                          | Purpose                               |
| ---------------------- | ---------------------------------------------------- | ------------------------------------- |
| `generate-user-models` | Updates the L2 user model with conversation insights | Enhanced personalization              |
| `extract-memories`     | Stores key information as L1 observations            | Future context recall                 |
| `compute-metrics`      | Calculates performance metrics                       | Quality and effectiveness measurement |

<details>

<summary>Processing architecture</summary>

* **Live sessions** handle real-time interaction.
* **Post-processing** performs deep analysis after a conversation.
* **No blocking.** The user experience stays responsive.
* **Async updates.** Memories, models, and metrics are updated in the background.

</details>


---

# Agent Instructions: 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:

```
GET https://docs.amigo.ai/developer-guide/classic-api/webhooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
