Guides
Webhook integration
Set up a webhook URL, verify messages, and see example payloads.
Outbound webhooks notify your systems when Furnace events occur. Furnace POSTs JSON to your HTTPS endpoint; your endpoint must return any 2xx response.
New to webhooks? Start with the short Webhooks concept page.
Quick start
- Open Account Settings → Webhooks (or a campaign override in Mission Control).
- Configure — paste an HTTPS URL, optionally set a signing secret, and select individual events (expand groups to pick specific types). Only selected event types are delivered.
- Click Next to open the Test step. Use View sample to inspect JSON for each event type, then Send test webhook to POST a sample to your URL.
- Click Done to save. Deliveries start immediately when matching events occur.
Campaign overrides replace the account URL (and optionally secret or enabled events) for that campaign only. Leave the override URL empty to inherit the account default.
Receiving webhooks
Furnace sends:
POST {your_url}
Content-Type: application/json
X-Furnace-Event: email.sent
X-Furnace-Delivery: {delivery_id}
X-Furnace-Signature: sha256=... # when a signing secret is configuredBody envelope:
{
"id": "event-uuid",
"type": "email.sent",
"occurred_at": "2026-06-25T12:00:00.000Z",
"data": { ... }
}id— unique event id (stable across delivery retries for that event).type— event constant (matchesX-Furnace-Event).occurred_at— ISO-8601 timestamp.data— event-specific payload (see Webhook events in the sidebar).
Test webhooks
When you use Send test webhook in Furnace, the payload uses real event types with "test": true inside data. The examples in this guide show the live shape (no test field).
Retries and failures
Furnace retries failed deliveries up to 3 times. Your endpoint must return any 2xx HTTP status. Non-2xx responses or network errors are recorded in Account Settings → Failed deliveries.
Use X-Furnace-Delivery as a unique delivery id for idempotency on your side.
Verifying signatures
When a signing secret is configured, Furnace sets X-Furnace-Signature to sha256= followed by the hex-encoded HMAC-SHA256 of the raw JSON request body (exact bytes POSTed).
Node.js example:
import crypto from 'node:crypto';
function verifyFurnaceSignature(secret, rawBody, signatureHeader) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}Most no-code tools (Zoho Flow, Zapier, Make) can ignore the signature and accept the POST directly.
Single actions vs bulk
A single action (adding one person, one send) fires its own event. A bulk action (an import, or anything touching more than one person at once) fires one completion event for the whole operation instead of one per person.
Per-row lead.created / lead.updated / lead.deleted events are never emitted during bulk processing.
Single actions
| Action | Event |
|---|---|
POST /v1/campaigns/{id}/leads (single) | lead.created / lead.updated |
PATCH …/leads/{leadId} | lead.updated |
DELETE …/leads/{leadId} | lead.deleted |
| Campaign pause/stop/resume | campaign.paused / campaign.stopped / campaign.resumed |
| Worker: email sent, reply, bounce | email.sent / reply.received / bounce.detected |
| Block list add or remove | blocklist.entry_added / blocklist.entry_removed |
| Thread category assign/change/clear | reply.categorized |
Bulk actions
| Operation | Completion event |
|---|---|
api_lead_import / csv_lead_import_staged | lead.bulk_import.completed |
add_to_campaign | lead.added_to_campaign.completed |
remove_from_campaign | lead.removed_from_campaign.completed |
remove_from_all_campaigns | lead.removed_from_all_campaigns.completed |
add_to_lead_list | lead.added_to_list.completed |
remove_from_lead_list | lead.removed_from_list.completed |
export_leads | lead.export.completed |
pause_enrollments | enrollment.pause_completed |
resume_enrollments | enrollment.resume_completed |
Sync bulk shortcuts use the same completion events with source: "sync" and job_id: null. Batch completion data matches the BatchCompletionWebhookPayload schema in the Schemas section.
enrollment.created and enrollment.updated are not emitted.
Campaign overrides
When a webhook event includes a campaign_id, Furnace resolves delivery settings in this order:
- URL — campaign
webhook_url_overrideif set, otherwise accountwebhook_url. If no URL is configured, the event is not delivered. - Signing secret — campaign override if set, otherwise account secret.
- Enabled events — campaign
webhook_enabled_events_overrideif set (array), otherwise accountwebhook_enabled_events. If the resolved list is empty, no events are delivered. If non-empty, only listed types are delivered.
When the campaign override URL is empty, the account URL and account signing secret are used.
Shared lead identity fields
Every lead-scoped email-activity event (email.sent, reply.received, reply.categorized, bounce.detected) repeats the same identity block so a CRM can match a contact without a follow-up API call. Block list email examples include this block when a lead exists; domain examples do not.
| Field | Notes |
|---|---|
email | Lead address for CRM matching. Reply events also keep from_email. |
mailbox_email | Sending or receiving inbox. |
campaign_name | Human-readable campaign name. |
first_name, last_name, full_name, company_name, title, website, linkedin_url | Present only when stored on the lead. title is promoted from custom_lead_data. |
custom_fields | Nested object of leads.custom_lead_data. Keys that collide with reserved fields stay nested. |
custom_fields_truncated | true only when custom_fields exceeded the 8 KB byte budget. |
Empty or whitespace-only values are omitted. Furnace never sends "" for these fields. custom_fields is capped at 8192 UTF-8 bytes; overflow keys are dropped and custom_fields_truncated is set. body_text is capped at 16,000 characters.
No-code tools (Zoho Flow, Zapier, Make)
- Create an incoming webhook trigger in your tool and copy its HTTPS URL.
- Paste the URL in Furnace Account Settings → Webhooks and enable Email activity (or other groups you need).
- On the Test step, send
email.sentorreply.receivedand map fields from the sample JSON. - No echo-token or custom verification handler is required.
Event payloads
Live JSON examples for every event type:
- Lead added / updated — Single-lead changes and bulk import or add-to-campaign completions.
- Lead lists / export — Saved-list membership and people export job completions.
- Lead removed — Single-lead deletes and bulk removal from one or all campaigns.
- Enrollment pause / resume — Manual enrollment holds and bulk pause/resume completions.
- Campaign status — Campaign paused, resumed, or stopped.
- Email activity — Sends, replies, categorization, and bounces.
- Block list — Emails and domains added to or removed from the account block list.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| No webhooks received | URL empty, event type filtered out, or campaign override blocking delivery |
| Test works, live events missing | Event group not enabled, or non-2xx response on live delivery |
| Duplicate deliveries | Retries after timeout; dedupe on X-Furnace-Delivery |
| Signature verification fails | Body parsed/re-serialized before verify; use raw body bytes |