---
title: Webhooks
description: Send real-time notifications to external services
---

## What are webhooks

Webhooks let you send HTTP requests to external services from within workflows. Use them to:

- Notify your CRM when workflows complete
- Update external databases with contact actions
- Trigger third-party automations
- Sync data across systems
- Track workflow progress in analytics tools

## Using webhooks in workflows

Add a **Webhook** step to any workflow:

1. Go to **Workflows**
2. Create or edit a workflow
3. Add a **Webhook** step
4. Configure the HTTP request
5. Connect to other steps

### Basic webhook configuration

```json
{
  "type": "WEBHOOK",
  "config": {
    "url": "https://your-api.com/webhook",
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer your_api_token"
    },
    "body": {
      "email": "{{email}}",
      "event": "workflow_completed",
      "contactId": "{{id}}"
    }
  }
}
```

### Supported HTTP methods

- **POST** — Most common, sends data to endpoint
- **PUT** — Update existing resource
- **PATCH** — Partial update
- **GET** — Retrieve data (rarely used in workflows)
- **DELETE** — Remove resource

## Using contact variables

Access contact data in your webhook using template variables:

```json
{
  "url": "https://crm.example.com/contacts",
  "method": "POST",
  "body": {
    "email": "{{email}}",
    "firstName": "{{data.firstName}}",
    "lastName": "{{data.lastName}}",
    "plan": "{{data.plan}}",
    "workflowName": "{{workflowName}}",
    "completedAt": "{{now}}"
  }
}
```

Available variables:
- `{{email}}` — Contact email
- `{{id}}` — Contact ID
- `{{data.fieldName}}` — Any custom data field
- `{{workflowName}}` — Current workflow name
- `{{now}}` — Current timestamp

## Common use cases

### Notify Slack

```json
{
  "url": "https://hooks.slack.com/services/YOUR/WEBHOOK/URL",
  "method": "POST",
  "body": {
    "text": "New user completed onboarding: {{email}}"
  }
}
```

### Update CRM

```json
{
  "url": "https://api.crm.com/contacts/{{data.crmId}}",
  "method": "PATCH",
  "headers": {
    "Authorization": "Bearer crm_api_token"
  },
  "body": {
    "onboardingCompleted": true,
    "lastEngaged": "{{now}}"
  }
}
```

### Track analytics

```json
{
  "url": "https://analytics.example.com/events",
  "method": "POST",
  "headers": {
    "X-API-Key": "analytics_key"
  },
  "body": {
    "event": "workflow_milestone",
    "userId": "{{email}}",
    "properties": {
      "workflow": "{{workflowName}}",
      "step": "purchase_completed"
    }
  }
}
```

### Trigger Zapier

```json
{
  "url": "https://hooks.zapier.com/hooks/catch/YOUR_WEBHOOK_ID/",
  "method": "POST",
  "body": {
    "email": "{{email}}",
    "firstName": "{{data.firstName}}",
    "event": "trial_ended"
  }
}
```

## Error handling

### Webhook failures

If a webhook request fails:
- Workflow continues to next step
- Error is logged in workflow execution
- Contact is not blocked

Check workflow execution logs to see webhook errors:

```bash
curl -X GET {{API_URL}}/workflows/workflow_id/executions \
  -H "Authorization: Bearer sk_your_secret_key"
```

### Timeouts

Webhooks timeout after 30 seconds. If your endpoint takes longer:
- Use async processing on your end
- Return 202 Accepted immediately
- Process in background job

### Retry logic

Webhooks are **not automatically retried**. If you need guaranteed delivery:
- Implement retry logic in your endpoint
- Use a message queue (SQS, RabbitMQ)
- Track webhook status in your database

## Receiving email event webhooks

Plunk tracks email events automatically (opens, clicks, bounces). Access them via:

### Events API

```bash
curl -X GET "{{API_URL}}/events?contactId=contact_id" \
  -H "Authorization: Bearer sk_your_secret_key"
```

Returns email events:
```json
{
  "events": [
    {
      "name": "email.opened",
      "data": { "emailId": "...", "timestamp": "..." }
    },
    {
      "name": "email.clicked",
      "data": { "emailId": "...", "url": "...", "timestamp": "..." }
    }
  ]
}
```

### Trigger workflows on email events

Create a workflow triggered by email events:

```json
{
  "triggerType": "EVENT",
  "triggerConfig": {
    "eventName": "email.clicked"
  }
}
```

Then use a webhook step to forward to your system.

## Security best practices

**Use HTTPS only** — Never send sensitive data over HTTP.

**Authenticate requests** — Include API tokens in headers:
```json
{
  "headers": {
    "Authorization": "Bearer your_secret_token"
  }
}
```

**Validate on receiving end** — Don't trust webhook data blindly. Verify it matches your expectations.

**Don't expose secrets** — Store API tokens as environment variables, not in workflow config.

**Rate limit your endpoint** — Protect against webhook floods.

## Testing webhooks

### Use webhook.site

For testing, use [webhook.site](https://webhook.site):

1. Go to webhook.site
2. Copy your unique URL
3. Use it in your workflow webhook step
4. Trigger the workflow
5. See the request in webhook.site

### Test with ngrok

For local development:

```bash
ngrok http 3000
```

Use the ngrok URL in your webhook configuration. Requests will tunnel to your local server.

## What's next

- [Build workflows](/guides/workflows) with webhook steps
- [Track events](/guides/events) to trigger webhooks
- [Monitor analytics](/guides/analytics) for webhook success rates
