Update wiki

This commit is contained in:
Dries Augustyns
2025-12-07 13:36:31 +01:00
parent 0003e44db8
commit 683356c17c
48 changed files with 633 additions and 8620 deletions
@@ -0,0 +1,46 @@
---
title: Email Attachments
description: Send files with emails
icon: Paperclip
---
## Limits
- Max 10 attachments per email
- Max 10MB total size
- Any file type supported
## API usage
```json
{
"to": "[email protected]",
"subject": "Your Invoice",
"body": "<p>Invoice attached.</p>",
"attachments": [
{
"filename": "invoice.pdf",
"content": "JVBERi0xLjQK...",
"contentType": "application/pdf"
}
]
}
```
## Attachment fields
| Field | Description |
|-------|-------------|
| `filename` | Display name (max 255 chars) |
| `content` | Base64-encoded file content |
| `contentType` | MIME type (e.g., `application/pdf`) |
## Base64 encoding
```javascript
import fs from 'fs';
const fileBuffer = fs.readFileSync('invoice.pdf');
const base64Content = fileBuffer.toString('base64');
```
@@ -1,37 +1,26 @@
---
title: Campaigns vs Workflows
description: Choose the right tool for sending emails
description: Choose the right sending method
icon: GitCompare
---
## When to use each
## Quick comparison
**Transactional API** (`/v1/send`) — Immediate one-off emails from your code
- Password resets, confirmations, receipts
- Triggered directly by user actions
- Instant delivery
**Campaigns** — One-time broadcasts to many contacts
- Newsletters, announcements, promotions
- Created in dashboard, send now or schedule
- No code required
**Workflows** — Automated multi-email sequences
- Onboarding series, abandoned cart, trial reminders
- Triggered by events, segment changes, or schedules
- Delays, conditions, multiple emails
| Need | Use |
|------|-----|
| Password reset now | **API** `/v1/send` |
| Monthly newsletter | **Campaign** |
| Welcome series over 3 days | **Workflow** |
| Order confirmation | **API** `/v1/send` |
| Abandoned cart recovery | **Workflow** |
## Transactional API
Send emails directly from your application code.
Immediate one-off emails from your code.
```javascript
await fetch('{{API_URL}}/v1/send', {
await fetch('/v1/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${PLUNK_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: user.email,
subject: 'Reset your password',
@@ -40,90 +29,19 @@ await fetch('{{API_URL}}/v1/send', {
});
```
**Use for:**
- Emails that must send immediately
- One-to-one triggered emails
- Context-specific data (reset tokens, order IDs)
## Campaigns
Create and send broadcasts in the dashboard.
One-time broadcasts to many contacts. Created in dashboard.
1. Create campaign
2. Write email content
3. Select audience (all contacts, segment, or filter)
4. Send now or schedule
**Use for:**
- One-time sends to many people
- Scheduled announcements
- Manual email sends
**Can't do:**
- Automation or triggers
- Multi-step sequences
- Delays between emails
- Send now or schedule
- Target all contacts, segments, or filters
- No code required
## Workflows
Build automated sequences with the visual workflow builder.
Automated multi-email sequences.
**Example workflow:**
```
[Trigger: user_signed_up]
[Send Email: Welcome]
[Delay: 24 hours]
[Send Email: Feature tour]
[Delay: 48 hours]
[Send Email: Help offer]
```
**Use for:**
- Multi-email sequences
- Time-delayed follow-ups
- Event-triggered automation
- Triggered by events or segment changes
- Delays between emails
- Conditional logic (if/then)
**Setup required:**
- Track events from your app via `/v1/track`
- Create workflow in dashboard
- Enable workflow
## Decision matrix
| Need | Use |
|------|-----|
| Send password reset now | Transactional API |
| Send monthly newsletter | Campaign |
| Send welcome series over 3 days | Workflow |
| Send order confirmation | Transactional API |
| Send announcement to all users | Campaign |
| Send trial reminder 3 days before expiration | Workflow |
| Send receipt after payment | Transactional API |
| Send seasonal promotion | Campaign |
| Send abandoned cart recovery (1hr + 24hr) | Workflow |
## Using them together
Most apps use all three:
**SaaS example:**
- **Transactional**: Password resets, email verification
- **Campaigns**: Monthly product updates
- **Workflows**: Trial onboarding, churn prevention
**E-commerce example:**
- **Transactional**: Order confirmations, shipping updates
- **Campaigns**: Weekly deals newsletter
- **Workflows**: Abandoned cart, review requests
## Next steps
- [Send a transactional email](/tutorials/first-transactional-email)
- [Create your first workflow](/tutorials/welcome-series-workflow)
- [Build a campaign](/tutorials/newsletter-campaign)
- Re-entry control
@@ -1,18 +1,10 @@
---
title: Contacts and Data
title: Contacts
description: Store and manage your audience
icon: Users
---
## What are contacts
Contacts are people in your email list. Each contact has:
- **Email** — Unique identifier
- **Subscription status** — Subscribed or unsubscribed
- **Custom data** — Any fields you need
## Data structure
## Structure
```json
{
@@ -27,188 +19,42 @@ Contacts are people in your email list. Each contact has:
}
```
The `data` field stores custom information as key-value pairs.
## Adding contacts
## Data types
- **Dashboard:** Contacts → Add Contact
- **CSV import:** Contacts → Import
- **API:** `POST /contacts`
- **Events:** Auto-created when tracking events
**Strings:**
```json
{ "firstName": "Sarah", "company": "Acme Inc" }
## Contact data
The `data` field stores custom key-value pairs.
**Best practices:**
- Use consistent naming (camelCase or snake_case)
- Store dates as ISO strings: `"2024-03-15T10:30:00Z"`
- Use numbers for numeric values (enables comparisons)
## Template variables
Use `{{fieldName}}` in emails:
```html
<p>Hello {{firstName}}!</p>
```
**Numbers:**
```json
{ "mrr": 99, "loginCount": 15 }
```
**Fallback:** `{{firstName ?? 'there'}}`
Store as numbers for `greaterThan`/`lessThan` comparisons.
**Reserved:** `{{email}}`, `{{id}}`
**Booleans:**
```json
{ "verified": true, "newsletter": false }
```
## Temporary data
**Dates:**
```json
{ "signupDate": "2024-03-15T10:30:00Z" }
```
Data that won't save to contact:
Use ISO 8601 format.
**Arrays:**
```json
{ "tags": ["vip", "enterprise"] }
```
**Objects:**
```json
{
"address": {
"city": "San Francisco",
"country": "US"
}
```javascript
data: {
resetCode: { value: 'ABC123', persistent: false }
}
```
Access nested fields: `address.country`
## Creating contacts
### Via API
```bash
curl -X POST {{API_URL}}/contacts \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"subscribed": true,
"data": {
"firstName": "Sarah",
"plan": "pro"
}
}'
```
### Automatic upsert
If email exists, updates instead of creating duplicate.
```javascript
// First call
POST /contacts { email: "[email protected]", data: { plan: "free" } }
// Second call - updates same contact
POST /contacts { email: "[email protected]", data: { mrr: 99 } }
// Result: { plan: "free", mrr: 99 }
```
Data merges automatically.
### Via event tracking
```bash
curl -X POST {{API_URL}}/v1/track \
-H "Authorization: Bearer pk_your_public_key" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"event": "signed_up",
"data": {
"plan": "pro",
"source": "landing"
}
}'
```
Creates contact if doesn't exist, updates if does.
## Updating contacts
```bash
curl -X PATCH {{API_URL}}/contacts/contact_id \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"data": {
"plan": "premium",
"mrr": 199
}
}'
```
New fields added, existing fields overwritten, unmentioned fields preserved.
### Remove fields
Set to `null`:
```json
{ "data": { "temporaryToken": null } }
```
## Subscription status
**Subscribed (true):**
- Receives marketing emails
- Receives transactional emails
**Unsubscribed (false):**
- Does NOT receive marketing emails
- Still receives transactional emails
Template type controls this behavior. See [Template Types](/concepts/templates-types).
## Using contact data
### In templates
```html
<h1>Hi {{firstName}}!</h1>
<p>Your {{plan}} plan renews on {{renewalDate}}.</p>
```
### In segments
Filter by data fields:
- `plan equals "premium"`
- `mrr greaterThan 100`
- `loginCount lessThan 5`
### In workflows
```
[Condition: plan equals "enterprise"]
├─ True → [Send: Enterprise content]
└─ False → [Send: Standard content]
```
## Best practices
**Consistent naming** — Use camelCase or snake_case, not both.
**Correct types** — Use `99` not `"99"` for numbers.
**ISO dates** — `"2024-03-15T10:30:00Z"` for date fields.
**Sync critical fields only** — Don't mirror entire database. Only fields used in emails, segments, or workflows.
**Update in real-time** — When user data changes, update contact immediately.
**Respect unsubscribes** — Never re-subscribe automatically.
## Deleting contacts
```bash
curl -X DELETE {{API_URL}}/contacts/contact_id \
-H "Authorization: Bearer sk_your_secret_key"
```
Permanent deletion. Consider unsubscribing instead to preserve history.
## Next steps
- [Create segments](/concepts/segments-and-filters)
- [Track events](/concepts/events-and-triggers)
- [Use templates](/concepts/templates-types)
Use for: one-time codes, tokens, session data.
@@ -0,0 +1,43 @@
---
title: Custom Domains
description: Send from your own domain
icon: Globe
---
## Why use custom domains
- Better deliverability
- Brand consistency
- Builds sender reputation
## Setup
1. Go to **Settings → Domains**
2. Click **Add Domain**
3. Enter your domain
4. Add the provided DNS records (DKIM, MX, TXT)
5. Wait for verification (usually 10-30 minutes, up to 48 hours)
## Using your domain
Specify in the `from` field when sending:
```json
{
"to": "[email protected]",
"from": "[email protected]",
"subject": "Order confirmed",
"body": "<p>Your order has been confirmed.</p>"
}
```
## Troubleshooting
**Domain won't verify:**
- Check DNS records are correct
- Wait for DNS propagation (up to 48 hours)
- Remove conflicting DKIM records from other services
**Emails going to spam:**
- Warm up new domains with small volumes first
- Monitor bounce and complaint rates
@@ -1,180 +1,53 @@
---
title: Events and Triggers
description: Track behavior and trigger workflows
title: Events
description: Track actions and trigger workflows
icon: Activity
---
## What are events
## Overview
Events track user actions from your application. Use them to update contact data, trigger workflows, and build segments.
Events track user actions. Use them to trigger workflows and update contact data.
## Tracking events
### Basic event
## Tracking
```javascript
await fetch('{{API_URL}}/v1/track', {
await fetch('/v1/track', {
method: 'POST',
headers: {
'Authorization': `Bearer ${PLUNK_PUBLIC_KEY}`,
'Content-Type': 'application/json'
'Authorization': 'Bearer pk_your_public_key'
},
body: JSON.stringify({
event: 'signed_up',
email: user.email
})
});
```
Creates or updates contact. Event is recorded.
### Event with data
```javascript
await fetch('{{API_URL}}/v1/track', {
method: 'POST',
headers: {
'Authorization': `Bearer ${PLUNK_PUBLIC_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
event: 'purchase_completed',
email: user.email,
data: {
plan: 'premium',
mrr: 99
}
data: { plan: 'pro' }
})
});
```
Updates contact data fields. Data persists on contact record.
Creates/updates contact and records the event.
## Public vs Secret keys
## Keys
### Public Key (pk_*)
- **Public key (pk_*):** Safe for client-side, only works with `/v1/track`
- **Secret key (sk_*):** Server-side only, full API access
- Safe for client-side code
- Only works with `/v1/track`
- Use in browser/mobile apps
## Event data
### Secret Key (sk_*)
- Server-side only
- Works with all endpoints
- Full API access
## Event naming
Use clear, past-tense verbs with underscores:
✅ `signed_up`, `purchase_completed`, `feature_activated`
❌ `signup`, `buy`, `feature`
## Workflow triggers
### Event trigger
Workflow starts when event is tracked.
```
Trigger: event = "signed_up"
```
Track event:
**Persistent (default):** Saved to contact
```javascript
track('signed_up', '[email protected]');
data: { plan: 'premium' }
```
Workflow starts for that contact.
### Wait for event
Workflow pauses until event occurs.
```
[Wait for Event: purchase_completed, timeout: 7 days]
├─ Event occurred → [Send: Thank you]
└─ Timeout → [Send: Discount offer]
```
## Common patterns
### User lifecycle
**Non-persistent:** Available only to workflow
```javascript
// Signup
track('signed_up', email, { source: 'landing' });
// First login
track('first_login', email, { lastLoginAt: new Date().toISOString() });
// Subscription
track('subscription_created', email, { plan: 'premium', mrr: 99 });
data: {
orderId: { value: 'order-123', persistent: false }
}
```
### E-commerce
## Automatic events
```javascript
// Cart abandonment
track('cart_abandoned', email, {
cartTotal: 149.99,
cartUrl: `https://store.com/cart/${cartId}`
});
// Purchase
track('purchase_completed', email, {
orderId: order.id,
orderTotal: order.total
});
```
## Event-based segments
Track events to update contact data, then segment on that data.
1. Track login:
```javascript
track('login', email, {
lastLoginAt: new Date().toISOString()
});
```
2. Create segment:
```
Field: lastLoginAt
Operator: greaterThan
Value: {{7_days_ago}}
```
Contacts auto-join segment when they log in.
## Testing events
**Dashboard:** Contacts → Search email → Events tab
Shows all events for that contact.
**Workflows:** Check executions after tracking event to verify workflow triggered.
## Rate limits
- Public key: 100 requests/minute
- Secret key: 1000 requests/minute
## Common issues
**Workflow not triggering:**
- Workflow is enabled
- Event name matches exactly (case-sensitive)
- Contact email is correct
**Contact data not updating:**
- Field names are case-sensitive
- Values are correct type (number vs string)
## Next steps
- [Build event-triggered workflows](/tutorials/welcome-series-workflow)
- [Create event-based segments](/concepts/segments-and-filters)
- [Track events from your app](/tutorials/event-tracking-integration)
Plunk tracks these automatically:
- `email.sent`, `email.opened`, `email.clicked`
- `email.bounced`, `email.complained`
- `segment.entered`, `segment.exited`
-49
View File
@@ -1,49 +0,0 @@
---
title: Core Concepts
description: Understand how Plunk works
icon: Lightbulb
---
## Email methods
<Cards>
<Card icon="GitCompare" title="Campaigns vs Workflows" href="/concepts/campaigns-vs-workflows">
When to use each email method
</Card>
</Cards>
## Data & contacts
<Cards>
<Card icon="Users" title="Contacts and Data" href="/concepts/contacts-and-data">
Store and manage your audience
</Card>
<Card icon="Filter" title="Segments" href="/concepts/segments-and-filters">
Create dynamic audience groups
</Card>
</Cards>
## Sending emails
<Cards>
<Card icon="Mail" title="Template Types" href="/concepts/templates-types">
Marketing vs Transactional templates
</Card>
<Card icon="Activity" title="Events and Triggers" href="/concepts/events-and-triggers">
Track behavior and trigger workflows
</Card>
</Cards>
## Performance & delivery
<Cards>
<Card icon="Send" title="Email Deliverability" href="/concepts/email-deliverability">
Reach the inbox
</Card>
<Card icon="Zap" title="Scale and Performance" href="/concepts/scale-and-performance">
Optimize for millions of contacts
</Card>
</Cards>
+4 -3
View File
@@ -1,13 +1,14 @@
{
"title": "Core Concepts",
"pages": [
"index",
"contacts-and-data",
"templates-types",
"campaigns-vs-workflows",
"segments-and-filters",
"events-and-triggers",
"email-deliverability",
"scale-and-performance"
"webhooks",
"custom-domains",
"attachments",
"troubleshooting"
]
}
@@ -1,150 +1,40 @@
---
title: Segments and Filters
description: Create dynamic audience groups
icon: Filter
title: Segments
description: Dynamic audience groups
icon: Funnel
---
## What are segments
## Overview
Segments are dynamic groups of contacts based on data filters. They update automatically when contact data changes.
Segments are dynamic groups based on filters. They update automatically when contact data changes.
Use segments to:
- Target specific audiences in campaigns
- Trigger workflows when contacts enter/exit
- Filter contacts in dashboard
## Creating
## Creating segments
1. Go to **Segments** → **Create Segment**
2. Name your segment
3. Add filter conditions
4. Save
### In dashboard
## Operators
**Contacts** → **Segments** → **Create Segment**
| Operator | Example |
|----------|---------|
| `equals` | `plan equals "pro"` |
| `not equals` | `plan not equals "free"` |
| `contains` | `email contains "@gmail"` |
| `greater than` | `mrr greater than 100` |
| `less than` | `loginCount less than 5` |
| `exists` | `company exists` |
1. Name your segment
2. Add filters
3. Save
## Combining filters
### Via API
- **AND:** All conditions must match
- **OR:** Any condition can match
```bash
curl -X POST {{API_URL}}/segments \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Premium Users",
"filters": [
{
"field": "plan",
"operator": "equals",
"value": "premium"
}
]
}'
```
## Membership tracking
## Filter operators
Enable **Track membership changes** to:
- Trigger workflows on segment entry/exit
- Generate `segment.entered` and `segment.exited` events
| Operator | Use | Example |
|----------|-----|---------|
| `equals` | Exact match | `plan equals "pro"` |
| `notEquals` | Not matching | `plan notEquals "free"` |
| `contains` | Substring (case-insensitive) | `email contains "@company.com"` |
| `greaterThan` | Number comparison | `mrr greaterThan 100` |
| `lessThan` | Number comparison | `loginCount lessThan 5` |
| `greaterThanOrEqual` | Inclusive comparison | `age greaterThanOrEqual 18` |
| `lessThanOrEqual` | Inclusive comparison | `daysInactive lessThanOrEqual 30` |
| `exists` | Field has value | `company exists` |
| `notExists` | Field missing/null | `lastName notExists` |
| `startsWith` | String prefix | `coupon startsWith "SAVE"` |
| `endsWith` | String suffix | `email endsWith ".edu"` |
## Multiple filters (AND logic)
All filters must match.
```json
{
"name": "Active Premium Users",
"filters": [
{ "field": "plan", "operator": "equals", "value": "premium" },
{ "field": "loginCount", "operator": "greaterThan", "value": 5 }
]
}
```
Contact matches only if: `plan = "premium"` AND `loginCount > 5`
## Dynamic updates
Segments update automatically.
**Example:**
1. Segment: `plan equals "premium"`
2. User upgrades: `plan` changes from "free" to "premium"
3. Contact auto-added to segment
No manual refresh needed.
## Workflow triggers
Trigger workflows when contacts enter/exit segments.
### Enable tracking
```bash
curl -X PATCH {{API_URL}}/segments/segment_id \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{"trackMembership": true}'
```
Only enable for segments used as triggers.
### Entry trigger
```
[Trigger: contact enters "Premium Users"]
[Send: Welcome to premium]
```
### Exit trigger
```
[Trigger: contact exits "Trial Users"]
[Send: Trial ended offer]
```
## Nested fields
Access nested data with dot notation.
```json
{
"field": "preferences.newsletter",
"operator": "equals",
"value": true
}
```
## Best practices
**Store correct types** — Use `99` not `"99"` for numbers. Use `true` not `"true"` for booleans.
**Track membership sparingly** — Only enable on segments used as workflow triggers.
**Name descriptively** — "High-Value Customers (MRR >$200)" not "Segment 3"
## Common issues
**Numeric filters not working** — Ensure field is stored as number, not string.
**Case-sensitive matching** — `equals "Pro"` doesn't match `plan = "pro"`. Use `contains` for case-insensitive.
**Missing field** — If field doesn't exist on contact, filter won't match.
## Next steps
- [Use segments in campaigns](/guides/campaigns)
- [Trigger workflows on segment entry](/guides/workflows)
- [Store contact data](/concepts/contacts-and-data)
Only enable for segments used as workflow triggers.
@@ -1,112 +1,36 @@
---
title: Template Types
description: Marketing vs Transactional templates
description: Marketing vs Transactional
icon: Mail
---
## Two template types
## Two types
| Template Type | Sends to Unsubscribed? | Use For |
|--------------|------------------------|---------|
| **Marketing** | No | Newsletters, promotions, announcements |
| **Transactional** | Yes | Receipts, confirmations, password resets |
| Type | Sends to Unsubscribed? | Use For |
|------|------------------------|---------|
| **Marketing** | No | Newsletters, promotions |
| **Transactional** | Yes | Receipts, password resets |
## Marketing templates
## Marketing
Only sends to subscribed contacts.
Only sends to subscribed contacts. Automatically includes unsubscribe link.
**Use for:** Newsletters, product updates, promotional emails.
## Transactional
**Behavior:**
```javascript
// Contact is unsubscribed
POST /v1/send { to: "[email protected]", template: "newsletter" }
// → Email NOT sent
```
Sends regardless of subscription status. Use only for essential emails.
## Transactional templates
Sends regardless of subscription status.
**Use for:** Order confirmations, password resets, account alerts.
**Behavior:**
```javascript
// Contact is unsubscribed
POST /v1/send { to: "[email protected]", template: "receipt" }
// → Email sent
```
## Setting template type
### In dashboard
**Templates** → Create/Edit → **Type** dropdown
### Via API
```bash
curl -X POST {{API_URL}}/templates \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Password Reset",
"type": "TRANSACTIONAL",
"subject": "Reset your password",
"body": "<p>Click: {{resetLink}}</p>"
}'
```
Type: `MARKETING` or `TRANSACTIONAL`
## Changing type
Update anytime:
```bash
curl -X PATCH {{API_URL}}/templates/template_id \
-H "Authorization: Bearer sk_your_secret_key" \
-H "Content-Type: application/json" \
-d '{"type": "MARKETING"}'
```
**Warning:** Changing transactional → marketing stops sending to unsubscribed contacts.
## Unsubscribe links
Marketing templates auto-include unsubscribe link in footer.
Custom placement:
```html
<a href="{{unsubscribeUrl}}">Unsubscribe</a>
```
Transactional templates don't need unsubscribe links.
## Choosing the right type
## Choosing
Ask: "Would user be frustrated if they didn't receive this after unsubscribing?"
**If yes → Transactional**
- Password resets
- Order confirmations
- Account alerts
- **Yes → Transactional** (password resets, order confirmations)
- **No → Marketing** (newsletters, promotions)
**If no → Marketing**
- Newsletters
- Product announcements
- Promotions
## Variables
## Best practices
```html
<h1>Hello {{firstName}}!</h1>
<p>Your {{plan}} plan renews on {{renewalDate}}.</p>
```
**Default to marketing** — Only use transactional for truly necessary emails.
**Don't abuse transactional** — Sending marketing content via transactional templates violates regulations and damages reputation.
**Test both states** — Verify behavior with subscribed and unsubscribed contacts.
## Next steps
- [Create templates](/guides/templates)
- [Manage subscriptions](/concepts/contacts-and-data)
- [Send emails](/tutorials/first-transactional-email)
**Fallback:** `{{firstName ?? 'there'}}`
@@ -0,0 +1,44 @@
---
title: Troubleshooting
description: Common issues and solutions
icon: Bug
---
## Authentication
**Invalid API key:**
- Secret keys start with `sk_`, public keys with `pk_`
- Use `Authorization: Bearer sk_your_secret_key`
**Wrong key type:**
- Public keys (`pk_`) only work with `/v1/track`
- All other endpoints require secret keys (`sk_`)
## Emails not sending
1. **Billing limit reached** — Check Settings → Billing
2. **Template not found** — Verify template ID exists
3. **Contact unsubscribed** — Marketing templates skip unsubscribed contacts
## Emails going to spam
1. Set up a custom domain
2. Clean your list (remove bounces)
3. Warm up new domains gradually
## Workflows not triggering
1. **Workflow not enabled** — Check toggle is ON
2. **Wrong event name** — Names are case-sensitive
3. **Already entered** — If re-entry disabled, contact can only enter once
## Rate limiting (429)
- Wait and retry
- Batch multiple recipients in one request
- Spread requests over time
## Request IDs
Every API response includes `X-Request-ID` header. Include this when contacting support.
@@ -0,0 +1,47 @@
---
title: Webhooks
description: Send HTTP requests from workflows
icon: Webhook
---
## Overview
Webhooks send HTTP requests to external services from within workflows.
## Adding a webhook step
1. Edit a workflow
2. Add a **Webhook** step
3. Configure URL, method, headers, and body
## Configuration
```json
{
"url": "https://your-api.com/webhook",
"method": "POST",
"headers": {
"Authorization": "Bearer your_api_token"
},
"body": {
"email": "{{email}}",
"firstName": "{{data.firstName}}"
}
}
```
**Methods:** POST, PUT, PATCH, GET, DELETE
## Variables
- `{{email}}`, `{{id}}` — Contact fields
- `{{data.fieldName}}` — Custom data
- `{{workflowName}}`, `{{now}}` — Workflow context
## Error handling
- Failed webhooks don't block the workflow
- Errors logged in execution details
- 30 second timeout
- No automatic retries