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

# Outreach Integration Guide

> End-to-end guide for pushing a message into the Outreach Agent and listening for replies via webhooks.

## The big picture

This guide walks through a complete integration: your platform receives a collab pitch, pushes it into the Sight AI Outreach Agent via the REST API, and receives real-time webhook notifications as the agent triages, replies, and tracks the conversation.

```
Your platform ──POST──> Sight AI API ──> Outreach Agent ──reply──> Prospect
     ^                       │                │
     │                       │                │
     └──── webhook <─────────┴────────────────┘
     (reply_sent, status_changed, contact_suppressed)
```

## Step 1: Register an OAuth client + authorize

1. Open **Integrations → Sight AI API → OAuth apps** in the Sight AI app.

2. Create a client with your app's name and your HTTPS redirect URI (e.g. `https://your-platform.com/oauth/callback`). For a server-side platform, check **Server-side app (issue a client secret)** to register a confidential client and copy the `client_id` + `client_secret` (`sai_ocsec_…`) shown once. Otherwise copy just the `client_id` (public client).

3. In your platform, add a "Connect Sight AI" button that redirects the user to Sight AI's authorize endpoint with the scopes you need (`webhooks:write`, `webhooks:read`, `inbox:write`) + PKCE:

   ```
   GET https://app.trysight.ai/oauth/authorize
     ?response_type=code&client_id=YOUR_CLIENT_ID
     &redirect_uri=https://your-platform.com/oauth/callback
     &scope=webhooks:write webhooks:read inbox:write
     &code_challenge=PKCE_CHALLENGE&code_challenge_method=S256&state=RANDOM
   ```

4. The user picks a site and approves the scopes on Sight AI's consent screen. Your redirect handler exchanges the code for a `sai_oauth_*` access token at `POST /oauth/token`.

See [Authentication](/developers/authentication) for the OAuth flow and scope model, and [Webhooks](/developers/webhooks#the-oauth-flow) for the full sequence.

## Step 2: Create the webhook subscription via API

After your platform has an access token, create the webhook subscription programmatically (no dashboard form needed):

```bash theme={null}
curl -X POST https://app.trysight.ai/api/v1/sites/site_abc123/outreach/webhooks \
  -H "Authorization: Bearer sai_oauth_..." \
  -H "Content-Type: application/json" \
  -d '{
    "label": "CRM — inbound handler",
    "url": "https://your-platform.com/webhooks/sightai",
    "eventTypes": ["outreach.reply_sent", "outreach.conversation_status_changed", "outreach.contact_suppressed"]
  }'
```

The response includes the **signing secret** (shown once). Store it in your environment as `SIGHTAI_WEBHOOK_SECRET`. See [Outreach Webhooks API](/developers/api-reference/outreach-webhooks) for the full reference, and [Webhooks](/developers/webhooks) for the event catalog.

## Step 3: Push a message

When your platform receives a collab pitch (e.g. a prospect emailed your support inbox), POST it to the ingestion endpoint:

```bash theme={null}
curl -s -X POST https://app.trysight.ai/api/v1/sites/site_abc123/outreach/inbox/messages \
  -H "Authorization: Bearer sai_oauth_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: pitch-2026-06-29-editor-example-com-001" \
  -d '{
    "from": "editor@example.com",
    "fromName": "Jane Editor",
    "subject": "Collab request for your SEO post",
    "bodyText": "Hi, I run example.com and loved your piece on X. Would you consider adding a link to our guide on Y? Happy to reciprocate.",
    "runExtraction": true
  }'
```

The response includes the `conversationId` and `opportunityId` Sight AI created. Store these so you can correlate webhook events back to the original pitch.

```json theme={null}
{
  "ok": true,
  "siteId": "site_abc123",
  "conversationIds": ["conv_xyz"],
  "opportunityIds": ["opp_001"],
  "entrySource": "forwarded_actionable",
  "aiPaused": false
}
```

See [Outreach Inbox](/developers/api-reference/outreach-inbox) for the full request/response reference.

<Note>
  Always send an <code>Idempotency-Key</code> header. If the request times out or fails, you can safely retry with the same key without creating a duplicate conversation.
</Note>

## Step 4: Verify the signature on incoming webhooks

Your webhook handler must verify the `SightAI-Signature` header before processing the payload. The header is in the format `t=<unix>,v1=<hex>` where the hex is HMAC-SHA256 over `${timestamp}.${raw_body}` using your signing secret.

```javascript theme={null}
import crypto from 'node:crypto';

export async function POST(request) {
  const rawBody = await request.text();
  const sig = request.headers.get('SightAI-Signature');
  const eventType = request.headers.get('SightAI-Event');

  if (!verifySignature(rawBody, sig, process.env.SIGHTAI_WEBHOOK_SECRET)) {
    return new Response('Invalid signature', { status: 401 });
  }

  const event = JSON.parse(rawBody);
  // Process the event...

  return new Response('ok', { status: 200 });
}

function verifySignature(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('=')),
  );
  const timestamp = Number(parts.t);
  const signature = parts.v1;
  if (!timestamp || !signature) return false;
  if (Math.floor(Date.now() / 1000) - timestamp > toleranceSeconds) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
```

See [Webhooks → Verifying the signature](/developers/webhooks#verifying-the-signature) for Python and more detail.

## Step 5: Handle `outreach.reply_sent`

When the agent sends a reply, you'll receive a `outreach.reply_sent` event:

```json theme={null}
{
  "id": "outreach.reply_sent:1719655200000:abc123",
  "type": "outreach.reply_sent",
  "siteId": "site_abc123",
  "teamId": "team_xyz",
  "timestamp": 1719655200000,
  "data": {
    "conversationId": "conv_xyz",
    "messageId": "msg_outbound_001",
    "mailgunMessageId": "<20260629...@mail-sightai.com>",
    "triage": "accept",
    "autoResolved": false,
    "sendOutcome": "sent"
  }
}
```

Use the `conversationId` to correlate this reply with the original pitch you pushed in Step 3. Update your CRM with the reply details (e.g. "Sight AI agent replied — accepted the collab request").

## Step 6: Handle `outreach.conversation_status_changed`

When the conversation reaches a terminal state, you'll receive this event:

```json theme={null}
{
  "type": "outreach.conversation_status_changed",
  "data": {
    "conversationId": "conv_xyz",
    "oldStatus": "open",
    "newStatus": "resolved",
    "resolvedBy": "agent",
    "resolutionOutcome": "accept"
  }
}
```

Map this to your CRM's deal stages:

* `resolved` → "Closed — collab accepted"
* `declined` → "Closed — declined"
* `awaiting_human` → "Needs human review"

## Step 7: Handle `outreach.contact_suppressed`

When a prospect opts out (or bounces / complains), you'll receive this event. **Remove them from your outbound lists immediately** — continuing to email a suppressed contact risks deliverability damage.

```json theme={null}
{
  "type": "outreach.contact_suppressed",
  "data": {
    "email": "prospect@example.com",
    "reason": "opted_out",
    "source": "inbound_classifier",
    "upgraded": false
  }
}
```

## Troubleshooting

| Problem                      | Cause                                                                | Fix                                                                                                    |
| ---------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `402` response               | Team subscription or site license problem                            | Ensure the team has an active subscription and the site is licensed                                    |
| `403 INSUFFICIENT_SCOPE`     | API key lacks `inbox:write`                                          | Re-create the key with the `inbox:write` scope                                                         |
| `429` response               | Rate limit or per-site daily ingestion cap                           | Slow down your request rate; contact support to raise the daily cap                                    |
| Webhooks not arriving        | Subscription auto-disabled or endpoint returning non-2xx             | Check the Developers page for auto-disabled status; verify your endpoint returns 200 within 10s        |
| Duplicate conversations      | Missing `Idempotency-Key` header on retries                          | Always send the same `Idempotency-Key` when retrying a failed request                                  |
| `aiPaused: true` in response | Agent is inactive, auto-reply is off, or sender is a noreply address | Turn on the Outreach Agent in Inbox → Settings; the message still lands in the inbox for manual triage |

## Next steps

* [Outreach Inbox API reference](/developers/api-reference/outreach-inbox)
* [Webhooks reference](/developers/webhooks)
* [Authentication](/developers/authentication)
* [Rate limits](/developers/rate-limits)
