# GoHighLevel Webhook Integration Guide for reversemask

## Overview

This guide shows how to integrate reversemask's reversible PII vault into GoHighLevel workflows using Custom Values and Webhooks.

## Prerequisites

1. **reversemask deployed** - Get your API base URL (e.g., `https://reversemask.yourdomain.com`)
2. **GHL Agency Account** - With access to Workflows and Custom Values
3. **Polar.sh License** - For production use (or Founding Pass)

---

## Step 1: Create Custom Values in GHL

Go to **Settings → Custom Values** and create these values:

| Custom Value Name | Value | Description |
|-------------------|-------|-------------|
| `reversemask_api_url` | `https://your-reversemask-domain.com` | Your reversemask deployment URL |
| `reversemask_license_key` | `your-polar-license-key` | From Polar.sh (or leave blank for demo) |

---

## Step 2: Create the Workflow

### Trigger: Inbound SMS / Form Submission

1. **New Workflow** → **Trigger: Customer Replied** (SMS) or **Form Submitted**
2. **Add Action: Custom Webhook** (this calls `/mask`)

### Webhook Action: `/mask`

| Field | Value |
|-------|-------|
| **Name** | `reversemask - Mask PII` |
| **URL** | `{{custom_values.reversemask_api_url}}/mask` |
| **Method** | `POST` |
| **Headers** | `Content-Type: application/json` |
| **Body (JSON)** | See below |

**Body JSON:**
```json
{
  "text": "{{contact.body}}",
  "known_pii": {
    "name": "{{contact.name}}",
    "email": "{{contact.email}}",
    "phone": "{{contact.phone}}",
    "address": "{{contact.address}}",
    "city": "{{contact.city}}",
    "state": "{{contact.state}}",
    "zip": "{{contact.postal_code}}"
  }
}
```

### Webhook Response Mapping

Save the response to **Custom Values** for use in next steps:

| Response Field | Custom Value Name |
|----------------|-------------------|
| `masked_text` | `reversemask_masked_text` |
| `vault_bundle` | `reversemask_vault_bundle` |
| `pii_detected` | `reversemask_pii_counts` |
| `injection_check.is_injection` | `reversemask_injection_blocked` |

---

## Step 3: LLM Agent Step

Use GHL's **Conversation AI** or a **Custom Webhook** to your LLM provider.

### If using Custom Webhook to OpenAI:

**Webhook Action: LLM Call**

| Field | Value |
|-------|-------|
| **Name** | `LLM Agent - Process Masked Input` |
| **URL** | `https://api.openai.com/v1/chat/completions` |
| **Method** | `POST` |
| **Headers** | `Authorization: Bearer {{custom_values.openai_api_key}}`<br>`Content-Type: application/json` |
| **Body** | See below |

**Body JSON:**
```json
{
  "model": "gpt-4o-mini",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant for booking appointments. When the user provides details, call the book_appointment tool."
    },
    {
      "role": "user",
      "content": "{{custom_values.reversemask_masked_text}}"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "book_appointment",
        "description": "Book an appointment for a lead",
        "parameters": {
          "type": "object",
          "properties": {
            "name": { "type": "string" },
            "email": { "type": "string" },
            "phone": { "type": "string" },
            "preferred_time": { "type": "string" }
          },
          "required": ["name", "email"]
        }
      }
    }
  ],
  "tool_choice": "auto"
}
```

**Response Mapping:** Save `tool_calls` to `llm_tool_calls` custom value.

---

## Step 4: Unmask Before CRM Action

**Webhook Action: `/unmask`**

| Field | Value |
|-------|-------|
| **Name** | `reversemask - Unmask Tool Call` |
| **URL** | `{{custom_values.reversemask_api_url}}/unmask` |
| **Method** | `POST` |
| **Headers** | `Content-Type: application/json` |
| **Body** | See below |

**Body JSON:**
```json
{
  "payload": "{{custom_values.llm_tool_calls}}",
  "vault_bundle": "{{custom_values.reversemask_vault_bundle}}"
}
```

**Response Mapping:** Save `unmasked_payload` to `unmasked_tool_args` custom value.

---

## Step 5: CRM / Calendar Action

Use the unmasked payload for your CRM action:

| Field | Value |
|-------|-------|
| **Action** | `Create Appointment` / `Update Contact` / `Create Opportunity` |
| **Data** | Map from `{{custom_values.unmasked_tool_args}}` |

Example for **Create Appointment**:
- **Contact Name**: `{{custom_values.unmasked_tool_args.name}}`
- **Email**: `{{custom_values.unmasked_tool_args.email}}`
- **Phone**: `{{custom_values.unmasked_tool_args.phone}}`
- **Date/Time**: `{{custom_values.unmasked_tool_args.preferred_time}}`

---

## Step 6: (Optional) Compliance Logging

Add a final **Custom Webhook** to trigger the weekly report or log the event:

**Webhook Action: Log Event**
```json
{
  "url": "{{custom_values.reversemask_api_url}}/guard",
  "method": "POST",
  "body": {
    "text": "{{contact.body}}"
  }
}
```

---

## Complete Workflow Diagram

```
[SMS/Form Trigger]
       ↓
[reversemask /mask]  ← known_pii from GHL contact fields
       ↓
[masked_text + vault_bundle]
       ↓
[LLM Agent (OpenAI/Claude)]  ← uses masked_text, calls tools with tokens
       ↓
[tool_calls with tokens]
       ↓
[reversemask /unmask]  ← uses vault_bundle
       ↓
[unmasked tool args with REAL data]
       ↓
[GHL Calendar / CRM Action]  ← works perfectly with real data!
       ↓
[Compliance Log] (optional)
```

---

## Testing

1. **Send test SMS**: "Hi, I'm John Doe, email john@gmail.com, phone 555-123-4567, book me for tomorrow 2pm"
2. **Check workflow run**:
   - `/mask` returns: `masked_text` with `lead_1@vault.local`, `vault_bundle`
   - LLM sees: "Hi, I'm [NAME_1], email lead_1@vault.local, book me for tomorrow 2pm"
   - LLM calls: `book_appointment(name="[NAME_1]", email="lead_1@vault.local", ...)`
   - `/unmask` restores: `book_appointment(name="John Doe", email="john@gmail.com", ...)`
   - GHL books appointment with REAL data ✅

---

## Error Handling

| Error | Cause | Fix |
|-------|-------|-----|
| `vault_bundle expired` | >5 min between /mask and /unmask | Reduce workflow steps; ensure unmask runs immediately after LLM |
| `InvalidTag` decryption | vault_bundle corrupted | Ensure vault_bundle passed exactly as returned (no trimming) |
| `injection_blocked = true` | Prompt injection detected | Review injection patterns; adjust if false positive |

---

## Security Notes

- **Never store** `vault_bundle` in GHL logs or database
- **5-minute TTL** on vault_bundle limits exposure window
- **Stateless** - no PII ever hits your database
- **License validation** - Production requires valid Polar.sh key

---

## Support

- **Demo Playground**: `GET https://your-reversemask-domain.com/`
- **API Docs**: `GET https://your-reversemask-domain.com/docs`
- **License Issues**: Check Polar.sh dashboard