# Webhook Triggers

Execute workflows automatically when external systems send HTTP requests to a unique webhook URL. Perfect for integrating with third-party services, processing real-time events, and building event-driven automation.

## Overview

Webhook triggers create a secure HTTP endpoint that external systems can call to start your workflow. When the webhook receives a request, it automatically creates a new workflow run with the request data as input.

**Key Features**:

* Unique, secure webhook URL for each trigger
* Multiple authentication methods (None, Basic, Bearer)
* Configurable HTTP methods (GET, POST, PUT, DELETE)
* Custom response codes
* Immediate response with workflow run tracking
* Full event history and logging

## How Webhook Triggers Work

### Execution Flow

```
External System → HTTP Request → Webhook URL → Trigger Validation →
→ Workflow Run Created → Immediate Response → Workflow Executes
```

1. **External system** sends HTTP request to webhook URL
2. **Platform validates** authentication and HTTP method
3. **Trigger creates** new workflow run with request data
4. **Immediate response** returned with run ID and status
5. **Workflow executes** asynchronously in background
6. **Event logged** with full request/response details

### Response Behavior

Webhooks use **immediate response mode**:

* HTTP response returned within milliseconds
* Response includes workflow run ID
* Workflow continues executing asynchronously
* External system doesn't wait for workflow completion

**Example webhook response**:

```json
{
  "workflow_run_id": "01HXXX123456789ABCDEF",
  "workflow_run_status": "QUEUED"
}
```

## Understanding Webhook Input Data

**CRITICAL CONCEPT**: The webhook request body is passed directly as your workflow's input. Understanding how to configure your input schema and access webhook data is essential for successful integration.

### How Webhook Data Becomes Workflow Input

When a webhook receives a request, the entire request body is automatically passed as the workflow's input data.

**The flow**:

1. External system sends JSON data to webhook URL
2. Platform receives the request body
3. Request body becomes workflow input
4. Fields defined in your input schema are accessible using `{{field_name}}`

### Configuring Workflow Input Schema for Webhooks

Your workflow's input schema defines what data the workflow expects. For webhook triggers, **you must define each field** you want to access in your workflow.

**Supported field types**:

* `string` - Text data
* `number` - Numeric values
* `boolean` - True/false values
* `array` - Lists of items
* `object` - Nested objects (accepts any structure)

#### Important: Object Type Limitation

**You must define each top-level field, but object fields accept any internal structure:**

* ✅ You must list each field in `properties`
* ✅ You can specify a field is type `object`
* ❌ You **cannot** define the internal structure/properties of an `object` field
* ✅ All nested data within `object` fields is accessible via dot notation

### Input Schema Examples

#### Example 1: Simple Fields

**Webhook payload**:

```json
{
  "customer_email": "user@example.com",
  "order_id": "ORD-12345",
  "amount": 99.99,
  "premium": true
}
```

**Workflow input schema** (you must define all fields):

```json
{
  "type": "object",
  "properties": {
    "customer_email": {
      "type": "string",
      "title": "Customer Email"
    },
    "order_id": {
      "type": "string",
      "title": "Order ID"
    },
    "amount": {
      "type": "number",
      "title": "Amount"
    },
    "premium": {
      "type": "boolean",
      "title": "Premium Customer"
    }
  }
}
```

**Access in workflow**:

```
{{customer_email}}  → "user@example.com"
{{order_id}}        → "ORD-12345"
{{amount}}          → 99.99
{{premium}}         → true
```

#### Example 2: Object Fields (No Internal Schema Needed)

**Webhook payload**:

```json
{
  "order_id": "ORD-789",
  "customer": {
    "id": "cust_123",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "address": {
      "city": "New York",
      "zip": "10001"
    }
  }
}
```

**Workflow input schema**:

```json
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "title": "Order ID"
    },
    "customer": {
      "type": "object",
      "title": "Customer Data"
    }
  }
}
```

**Key point**:

* We **must** define `customer` as a field in properties
* We specify `customer` is type `object`
* We **don't** need to define its internal properties (`id`, `name`, `email`, `address`)
* The system automatically accepts any structure inside the `customer` object

**Access nested data with dot notation**:

```
{{order_id}}                → "ORD-789"
{{customer.id}}             → "cust_123"
{{customer.name}}           → "Jane Smith"
{{customer.email}}          → "jane@example.com"
{{customer.address.city}}   → "New York"
{{customer.address.zip}}    → "10001"
```

Even though we didn't define `id`, `name`, `email`, or `address` in the schema, we can still access them because `customer` is type `object`!

#### Example 3: Array Fields

**Webhook payload**:

```json
{
  "order_id": "ORD-456",
  "items": [
    {"sku": "PROD-001", "quantity": 2, "price": 29.99},
    {"sku": "PROD-002", "quantity": 1, "price": 49.99}
  ],
  "tags": ["urgent", "vip", "international"]
}
```

**Workflow input schema**:

```json
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "title": "Order ID"
    },
    "items": {
      "type": "array",
      "title": "Order Items"
    },
    "tags": {
      "type": "array",
      "title": "Tags"
    }
  }
}
```

**Access in workflow**:

```
{{order_id}}          → "ORD-456"
{{items}}             → Full array of objects
{{items[0].sku}}      → "PROD-001"
{{items[0].quantity}} → 2
{{items[1].price}}    → 49.99
{{tags}}              → ["urgent", "vip", "international"]
{{tags[0]}}           → "urgent"
```

### Strategy: Define Top-Level Fields You Need

**Best Practice**: Define each top-level field from the webhook payload that your workflow will access. Use type `object` for complex nested structures.

**Example - Stripe webhook**:

Webhook sends this payload:

```json
{
  "id": "evt_123",
  "type": "charge.succeeded",
  "data": {
    "object": {
      "id": "ch_456",
      "amount": 5000,
      "currency": "usd",
      "customer": "cus_789",
      "receipt_email": "customer@example.com",
      "metadata": {
        "order_id": "ORD-12345",
        "product": "Premium Plan"
      }
    }
  },
  "created": 1234567890,
  "livemode": false
}
```

**Your workflow input schema** (define what you'll use):

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "title": "Event ID"
    },
    "type": {
      "type": "string",
      "title": "Event Type"
    },
    "data": {
      "type": "object",
      "title": "Event Data"
    }
  }
}
```

Even though we only defined `id`, `type`, and `data` (as object), you can still access all nested data:

```
{{type}}                                 → "charge.succeeded"
{{data.object.amount}}                   → 5000
{{data.object.receipt_email}}            → "customer@example.com"
{{data.object.metadata.order_id}}        → "ORD-12345"
{{data.object.metadata.product}}         → "Premium Plan"
```

The `data` field is defined as type `object`, so all its nested content is automatically accessible!

### Accessing Webhook Data in Workflow Nodes

Once your input schema is configured, reference webhook data using parameter substitution syntax: `{{field_name}}`

#### Simple Fields

```
{{customer_email}}
{{order_id}}
{{amount}}
{{premium}}
```

#### Nested Fields (Dot Notation)

```
{{customer.name}}
{{customer.email}}
{{customer.address.city}}
{{data.object.amount}}
{{metadata.order_id}}
```

#### Array Elements

```
{{items[0].sku}}
{{items[1].price}}
{{tags[0]}}
```

#### Use in Node Configurations

**Email node example**:

```json
{
  "to": "{{customer.email}}",
  "subject": "Order {{order_id}} Confirmation",
  "body": "Hello {{customer.name}}, your order total is ${{amount}}"
}
```

**API call node example**:

```json
{
  "url": "https://api.example.com/orders",
  "method": "POST",
  "body": {
    "order_id": "{{order_id}}",
    "customer_email": "{{customer.email}}",
    "total": "{{amount}}"
  }
}
```

### Processing Complex Data with JavaScript

For complex transformations or accessing deeply nested data, use JavaScript nodes:

**JavaScript node example**:

```javascript
// Access the full customer object
const customer = {{customer}};

// Extract specific fields
const fullName = customer.name;
const email = customer.email;
const city = customer.address.city;

// Process arrays
const items = {{items}};
const total = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const productNames = items.map(item => item.sku).join(", ");

// Return processed data
return {
  fullName,
  email,
  city,
  total,
  productNames,
  itemCount: items.length
};
```

### Real-World Integration Examples

#### Example 1: Stripe Payment Webhook

**Stripe payload structure**:

```json
{
  "id": "evt_1ABC123",
  "type": "charge.succeeded",
  "data": {
    "object": {
      "id": "ch_3XYZ789",
      "amount": 5000,
      "currency": "usd",
      "receipt_email": "customer@example.com",
      "metadata": {
        "order_id": "ORD-12345"
      }
    }
  }
}
```

**Your workflow input schema**:

```json
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "title": "Event ID"
    },
    "type": {
      "type": "string",
      "title": "Event Type"
    },
    "data": {
      "type": "object",
      "title": "Event Data"
    }
  }
}
```

**Workflow logic**:

1. **Node 1** - Validate event type:

   ```javascript
   const eventType = {{type}};
   if (eventType !== "charge.succeeded") {
     throw new Error("Unsupported event type: " + eventType);
   }
   ```
2. **Node 2** - Send confirmation email:

   ```
   To: {{data.object.receipt_email}}
   Subject: Payment Received - Order {{data.object.metadata.order_id}}
   Body: Thank you! We received your payment of ${{data.object.amount}}.
   ```
3. **Node 3** - Update database with order status:

   ```
   Order ID: {{data.object.metadata.order_id}}
   Amount: {{data.object.amount}}
   Currency: {{data.object.currency}}
   Status: "paid"
   ```

#### Example 2: GitHub Pull Request Webhook

**GitHub payload**:

```json
{
  "action": "opened",
  "number": 42,
  "pull_request": {
    "title": "Add new feature",
    "user": {
      "login": "developer123"
    },
    "head": {
      "ref": "feature-branch",
      "sha": "abc123"
    },
    "base": {
      "ref": "main"
    }
  },
  "repository": {
    "full_name": "org/my-project"
  }
}
```

**Your workflow input schema**:

```json
{
  "type": "object",
  "properties": {
    "action": {
      "type": "string",
      "title": "Action"
    },
    "number": {
      "type": "number",
      "title": "PR Number"
    },
    "pull_request": {
      "type": "object",
      "title": "Pull Request Data"
    },
    "repository": {
      "type": "object",
      "title": "Repository"
    }
  }
}
```

**Access data in workflow**:

```
{{action}}                       → "opened"
{{number}}                       → 42
{{pull_request.title}}           → "Add new feature"
{{pull_request.user.login}}      → "developer123"
{{pull_request.head.ref}}        → "feature-branch"
{{pull_request.head.sha}}        → "abc123"
{{repository.full_name}}         → "org/my-project"
```

**Example workflow**:

1. **Node 1** - Post PR comment:

   ```
   Message: "Thanks @{{pull_request.user.login}} for PR #{{number}}!
            Automated checks are running..."
   ```
2. **Node 2** - Notify team on Slack:

   ```
   Channel: #pull-requests
   Message: "New PR in {{repository.full_name}}: {{pull_request.title}}
            Branch: {{pull_request.head.ref}} → {{pull_request.base.ref}}"
   ```

#### Example 3: Form Submission

**Form payload**:

```json
{
  "first_name": "John",
  "last_name": "Doe",
  "email": "john@example.com",
  "company": "Acme Corp",
  "message": "I'm interested in your services"
}
```

**Your workflow input schema**:

```json
{
  "type": "object",
  "properties": {
    "first_name": {
      "type": "string",
      "title": "First Name"
    },
    "last_name": {
      "type": "string",
      "title": "Last Name"
    },
    "email": {
      "type": "string",
      "title": "Email"
    },
    "company": {
      "type": "string",
      "title": "Company"
    },
    "message": {
      "type": "string",
      "title": "Message"
    }
  }
}
```

**Use in workflow**:

```
Full Name: {{first_name}} {{last_name}}
Email: {{email}}
Company: {{company}}
Inquiry: {{message}}
```

**Example workflow**:

1. **Node 1** - Send auto-reply:

   ```
   To: {{email}}
   Subject: Thanks for contacting us, {{first_name}}!
   Body: We received your message and will get back to you soon.
   ```
2. **Node 2** - Create CRM lead:

   ```
   Name: {{first_name}} {{last_name}}
   Email: {{email}}
   Company: {{company}}
   Source: "Website Contact Form"
   ```
3. **Node 3** - Notify sales team:

   ```
   Channel: #new-leads
   Message: "New inquiry from {{first_name}} {{last_name}} at {{company}}"
   ```

## Common Mistakes and Solutions

### Mistake 1: Trying to Define Object Properties

**Problem** (❌ This doesn't work):

```json
{
  "type": "object",
  "properties": {
    "customer": {
      "type": "object",
      "properties": {  // ❌ NOT supported!
        "name": {"type": "string"},
        "email": {"type": "string"}
      }
    }
  }
}
```

**Solution** (✅ Correct):

```json
{
  "type": "object",
  "properties": {
    "customer": {
      "type": "object",  // ✅ Just define as object
      "title": "Customer Data"
    }
  }
}
```

You can still access `{{customer.name}}` and `{{customer.email}}` - you just don't define them in the schema!

### Mistake 2: Not Defining a Field at All

**Problem**:

```json
// Webhook sends: {"order_id": "123", "customer": {...}}

// Input schema (missing customer field):
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string"
    }
    // ❌ customer field not defined
  }
}

// In workflow:
{{customer.name}}  // ❌ Won't work - customer not defined
```

**Solution** (✅ Define all top-level fields):

```json
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string"
    },
    "customer": {
      "type": "object"  // ✅ Now customer.name is accessible
    }
  }
}
```

### Mistake 3: Field Name Mismatch

**Problem**:

```
Webhook sends: {"customer_email": "user@example.com"}
Schema defines: {"user_email": {"type": "string"}}
Workflow uses: {{user_email}}  // ❌ Returns undefined
```

**Solution**: Match field names exactly:

```json
{
  "type": "object",
  "properties": {
    "customer_email": {  // ✅ Exact match
      "type": "string",
      "title": "Customer Email"
    }
  }
}
```

### Mistake 4: Case Sensitivity

**Problem**:

```
Webhook sends: {"CustomerEmail": "user@example.com"}
Schema defines: {"customeremail": {...}}
Workflow uses: {{customeremail}}  // ❌ Case doesn't match
```

**Solution**: Field names are case-sensitive:

```json
{
  "type": "object",
  "properties": {
    "CustomerEmail": {  // ✅ Match exact case
      "type": "string"
    }
  }
}
```

Use in workflow:

```
{{CustomerEmail}}  ✅ Correct
{{customeremail}}  ❌ Wrong case
```

### Mistake 5: Wrong Data Type

**Problem**:

```
Webhook sends: {"amount": "99.99"}  (string)
Schema defines: {"amount": {"type": "number"}}
// May cause type mismatch issues
```

**Solution**: Match the actual type the webhook sends:

```json
{
  "type": "object",
  "properties": {
    "amount": {
      "type": "string"  // ✅ Match webhook's type
    }
  }
}
```

Then convert in workflow if needed:

```javascript
const amountNumber = parseFloat({{amount}});
```

## Testing Your Webhook Integration

### Step 1: Identify Webhook Payload Structure

Before creating your input schema, understand what data the webhook will send:

1. **Check third-party documentation** (Stripe, GitHub, etc.)
2. **Capture a test webhook** using tools like:
   * webhook.site
   * RequestBin
   * ngrok
3. **Note all top-level field names**
4. **Identify which fields are objects, arrays, or primitives**

### Step 2: Create Input Schema

Based on the payload structure, create your input schema:

```json
{
  "type": "object",
  "properties": {
    "field1": {"type": "string"},
    "field2": {"type": "number"},
    "field3": {"type": "object"},  // For nested data
    "field4": {"type": "array"}    // For lists
  }
}
```

### Step 3: Send Test Request

```bash
curl -X POST https://api.agenticflow.com/webhook/{your-path-id} \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token" \
  -d '{
    "field1": "test_value",
    "field2": 123,
    "field3": {
      "nested": "data"
    },
    "field4": ["item1", "item2"]
  }'
```

### Step 4: Check Trigger Event Log

1. Workflow → Triggers → Your webhook → Events tab
2. Find your test request
3. Inspect `request_body`
4. Verify all fields are present

### Step 5: Verify Workflow Execution

1. Click `workflow_run_id` from event
2. Check workflow run "Input Data"
3. Confirm fields are accessible
4. Review node execution logs

### Step 6: Add Debug Node

Add a JavaScript node to test field access:

```javascript
console.log("field1:", {{field1}});
console.log("field2:", {{field2}});
console.log("field3.nested:", {{field3.nested}});
console.log("field4[0]:", {{field4[0]}});

return {
  field1: {{field1}},
  field2: {{field2}},
  nested_value: {{field3.nested}},
  first_item: {{field4[0]}}
};
```

## Creating a Webhook Trigger

### Step 1: Navigate to Triggers

1. Open your workflow
2. Click "Triggers" tab
3. Click "Create Trigger"
4. Select "Webhook" type

### Step 2: Configure Basic Settings

**Name** (required):

```
Example: "Stripe Payment Webhook"
```

**Description** (optional):

```
Example: "Processes payment.succeeded events from Stripe"
```

### Step 3: Configure HTTP Settings

**HTTP Method**:

* **POST** (recommended) - Data in request body
* **GET** - Data in query parameters
* **PUT** - Update operations
* **DELETE** - Delete operations

**Response Code**:

* **200** - Standard success (default)
* **201** - Resource created
* **202** - Accepted for processing
* **204** - Success, no content

**Response Mode**:

* **Immediate** - Returns response immediately with workflow run ID

### Step 4: Configure Authentication

**Bearer Token** (Recommended for production):

```json
{
  "auth_type": "BEARER",
  "token": "your_secret_token"
}
```

Callers must include:

```
Authorization: Bearer your_secret_token
```

**Basic Authentication**:

```json
{
  "auth_type": "BASIC",
  "username": "user",
  "password": "pass"
}
```

Callers must include:

```
Authorization: Basic <base64(username:password)>
```

**No Authentication** (Testing only):

```json
{
  "auth_type": "NONE"
}
```

⚠️ Not recommended for production.

### Step 5: Save and Get Webhook URL

1. Click "Create Trigger"
2. Copy generated webhook URL:

   ```
   https://api.agenticflow.com/webhook/{unique-path-id}
   ```
3. Configure URL in external system
4. Webhook is immediately active

## Using Your Webhook

### Making Requests

**Basic POST request**:

```bash
curl -X POST https://api.agenticflow.com/webhook/{path-id} \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_token" \
  -d '{
    "customer_email": "user@example.com",
    "order_id": "ORD-123",
    "amount": 99.99
  }'
```

**Response**:

```json
{
  "workflow_run_id": "01HXXX123456789ABCDEF",
  "workflow_run_status": "QUEUED"
}
```

## Security Best Practices

1. **Always use authentication** in production (Bearer token recommended)
2. **Use strong, random tokens** (32+ characters minimum)
3. **Rotate credentials regularly** (every 90 days)
4. **Monitor webhook events** for suspicious activity
5. **Validate input data** in workflow nodes
6. **Use HTTPS** (enforced automatically)
7. **Never commit tokens** to version control

## Managing Webhooks

**Enable/Disable**:

* **Active**: Accepts requests, creates workflow runs
* **Inactive**: Returns 404, doesn't execute

**Update**:

* Change name, description
* Update authentication credentials
* Modify HTTP method or response code
* Toggle active status

**Delete**:

* Permanent action - cannot be undone
* Webhook URL becomes invalid immediately
* External systems will receive 404 errors

## Webhook Events

Every webhook request is logged with complete details:

**Event information**:

* Timestamp of request
* HTTP method, URL, headers
* Full request body
* Response status code and body
* Execution status (success/failed)
* Workflow run ID (if created)
* Error messages (if any)

**View events**: Workflow → Triggers → Select webhook → Events tab

**Use event logs for**:

* Debugging integration issues
* Monitoring webhook activity
* Auditing workflow executions
* Investigating failed requests

## Troubleshooting

### Field Returns Undefined

**Check**:

1. Is the field defined in input schema properties?
2. Does the field name match exactly (case-sensitive)?
3. Is the webhook actually sending that field?
4. Check trigger event logs to see actual payload received

### Nested Data Not Accessible

**Problem**: Can't access `{{customer.email}}`

**Check**:

1. Is `customer` defined as type `object` in schema?
2. Are you using correct dot notation?
3. Did webhook actually send nested data?

### Type Mismatch Errors

**Solution**: Ensure schema type matches what webhook sends:

* If webhook sends `"123"` (string), use `type: "string"`
* If webhook sends `123` (number), use `type: "number"`
* Convert types in JavaScript node if needed

## API Reference

### Create Webhook Trigger

```http
POST /workflows/{workflow_id}/triggers
Content-Type: application/json
Authorization: Bearer your_api_key
```

**Request**:

```json
{
  "name": "My Webhook",
  "description": "Webhook description",
  "trigger_type": "webhook",
  "trigger_config": {
    "auth_config": {
      "auth_type": "BEARER",
      "token": "secret_token"
    },
    "method": "POST",
    "response_code": 200,
    "response_mode": "immediate"
  }
}
```

**Response**:

```json
{
  "id": "01HXXX123456789ABCDEF",
  "workflow_id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "My Webhook",
  "trigger_config": {
    "path": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "method": "POST",
    "response_code": 200
  },
  "is_active": true,
  "created_at": "2024-01-15T10:30:00Z"
}
```

***

## Related Documentation

* [Triggers Overview](https://docs.agenticflow.ai/workflows/triggers) - All trigger types
* [Schedule Triggers](https://docs.agenticflow.ai/workflows/triggers/schedule-triggers) - Time-based triggers
* [Workflow Inputs](https://github.com/PixelML/agenticflow-docs/blob/main/docs/04-workflows/building-blocks/inputs/README.md) - Input configuration guide
* [Data Flow & Types](https://docs.agenticflow.ai/workflows/data-flow-and-types) - Working with data in workflows

***

**Ready to integrate external systems?** Create your first webhook trigger and configure your workflow inputs to match your webhook payload structure.
