Complete REST API documentation for WarmySender. Build custom integrations with our cold email, email warmup, campaign, and LinkedIn automation APIs.
The full machine-readable contract is published as an OpenAPI 3.1 document: Download the OpenAPI 3.1 spec. Import it into Postman, Insomnia, Swagger UI, or Redoc to generate a client, explore every endpoint interactively, or scaffold typed SDKs. The reference below is generated from the same source, so it always matches the spec and production.
All API requests require a Bearer token in the Authorization header. Generate API keys from Settings > API Keys in your WarmySender dashboard. Keys are scoped to your workspace and support granular permissions.
Authorization: Bearer your-api-key-here
All API endpoints are available at:
https://warmysender.com/api/v1
API requests are limited to 60 requests per minute per workspace. Rate limit headers are included in all responses:
X-RateLimit-Limit — Maximum requests per windowX-RateLimit-Remaining — Remaining requests in current windowX-RateLimit-Reset — Unix timestamp when the window resetsRetry-After — Seconds to wait (only on 429 responses)API keys are scoped to your workspace with granular permissions. Each endpoint lists the scope it needs.
mailboxes:read — List and read connected mailboxes.mailboxes:write — Create, update, test, delete, and restore mailboxes.warmup:read — Read warmup stats and per-mailbox warmup health.warmup:write — Enable/disable warmup and change warmup strategy or volume.jobs:read — Read the status of asynchronous jobs.prospects:read — List and read prospects.prospects:write — Create and update prospects.campaigns:read — List and read campaigns and their steps.campaigns:write — Create, update, start, and pause campaigns.enrollments:write — Enroll and unenroll prospects in campaigns.suppressions:read — Read the suppression (blocklist) entries.suppressions:write — Suppress prospects so they are never contacted.webhooks:read — List configured webhook endpoints.webhooks:write — Create, update, delete, and test webhook endpoints.linkedin:read — Read connected LinkedIn accounts and post reactions.linkedin:write — Perform LinkedIn engagement actions (e.g. react to posts).Every endpoint below is generated from the same source as our OpenAPI 3.1 specification, so the request bodies, response shapes, parameters, and scopes always match production. Base URL for all paths: https://warmysender.com/api/v1.
GET /api/v1/me
Returns the API key and workspace that the Bearer token belongs to, including the key's granted scopes. Useful for verifying a key and discovering its permissions. This is the one endpoint whose response is NOT wrapped in the standard data envelope.
{
"apiKey": {
"id": "key_8f1c2a",
"name": "Production integration",
"scopes": [
"mailboxes:read",
"mailboxes:write",
"warmup:read"
]
},
"workspace": {
"id": "ws_4b9d10"
}
}
unauthorized — Missing or invalid API key.GET /api/v1/mailboxes
Returns connected mailboxes, newest first, with cursor pagination. Soft-deleted mailboxes are excluded unless you filter by status=deleted.
mailboxes:readpagination.next_cursor.limit — in: query — type: integer — optional — default: 50 — range: 1–100. Results per page (1–100).cursor — in: query — type: string — optional. next_cursor from the previous page.external_id — in: query — type: string — optional. Exact-match filter on your external_id.email_address — in: query — type: string — optional. Case-insensitive substring match on the email address.status — in: query — type: string — optional — one of: created, testing, connected, error, deleted. Filter by status.warmup_enabled — in: query — type: string — optional — one of: true, false. Filter by warmup on/off.{
"data": [
{
"id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"external_id": "crm-contact-8841",
"email_address": "[email protected]",
"display_name": "Jordan Lee",
"status": "connected",
"health_status": "ok",
"health_reasons": null,
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 62,
"sending_enabled": true,
"daily_send_limit": 50,
"last_tested_at": "2026-06-10T08:31:00.000Z",
"last_test_error": null,
"signature": null,
"created_at": "2026-06-06T10:43:01.357Z",
"updated_at": "2026-06-10T08:31:05.220Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
GET /api/v1/mailboxes/{id}
Fetch a single mailbox by id. Poll this after create/restore until status becomes "connected".
mailboxes:readid — in: path — type: string — required. Mailbox id (UUID).{
"data": {
"id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"external_id": "crm-contact-8841",
"email_address": "[email protected]",
"display_name": "Jordan Lee",
"status": "connected",
"health_status": "ok",
"health_reasons": null,
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 62,
"sending_enabled": true,
"daily_send_limit": 50,
"last_tested_at": "2026-06-10T08:31:00.000Z",
"last_test_error": null,
"signature": null,
"created_at": "2026-06-06T10:43:01.357Z",
"updated_at": "2026-06-10T08:31:05.220Z"
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
not_found — No mailbox with that id in your workspace.POST /api/v1/mailboxes
Adds a mailbox and runs an asynchronous connection test (status moves created → testing → connected or error). If the email matches a previously soft-deleted mailbox, that mailbox is restored instead and the response is 200 with its preserved warmup history (otherwise 201).
mailboxes:writeemail_address — type: string — required — format: email. The mailbox email address.smtp_host — type: string — required. SMTP server hostname, e.g. smtp.gmail.com.smtp_port — type: integer — optional — default: 587 — range: 1–65535. SMTP port. One of 25, 465, 587. Defaults to 587.smtp_username — type: string — required. SMTP login (usually the email address).smtp_password — type: string — required — format: password. SMTP password or app password.imap_host — type: string — required. IMAP server hostname, e.g. imap.gmail.com.imap_port — type: integer — optional — default: 993 — range: 1–65535. IMAP port. One of 143, 993. Defaults to 993.imap_username — type: string — required. IMAP login (usually the email address).imap_password — type: string — required — format: password. IMAP password or app password.external_id — type: string — optional — range: –255. Your own reference id for this mailbox (unique per workspace).display_name — type: string — optional — range: –255. Display name shown on outgoing mail.sending_enabled — type: boolean — optional — default: true. Whether the mailbox may send. Defaults to true.daily_send_limit — type: integer — optional — default: 50 — range: 1–1000. Max sends per day. Defaults to 50.auto_enable_warmup — type: boolean — optional — default: false. Enable warmup automatically once the connection test passes.{
"email_address": "[email protected]",
"smtp_host": "smtp.gmail.com",
"smtp_port": 587,
"smtp_username": "[email protected]",
"smtp_password": "app-password",
"imap_host": "imap.gmail.com",
"imap_port": 993,
"imap_username": "[email protected]",
"imap_password": "app-password",
"external_id": "crm-contact-8841",
"auto_enable_warmup": true
}
{
"data": {
"id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"external_id": "crm-contact-8841",
"email_address": "[email protected]",
"display_name": "Jordan Lee",
"status": "testing",
"health_status": "ok",
"health_reasons": null,
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 0,
"sending_enabled": true,
"daily_send_limit": 50,
"last_tested_at": null,
"last_test_error": null,
"signature": null,
"created_at": "2026-06-06T10:43:01.357Z",
"updated_at": "2026-06-10T08:31:05.220Z"
},
"meta": {
"request_id": "req_mq28denhy91fh6",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}
}
validation_error — Body failed validation.mailbox_limit_exceeded — At or above your plan's mailbox limit.duplicate_email — An active mailbox already uses this email.duplicate_external_id — The external_id is already in use.ssrf_blocked — A host resolved to a private/internal IP.invalid_port — A port outside the allowed list (25/465/587/143/993).POST /api/v1/mailboxes/test
Synchronously tests SMTP + IMAP credentials WITHOUT creating a mailbox. Rate limited.
mailboxes:writeemail_address — type: string — required — format: email. The mailbox email address.smtp_host — type: string — required. SMTP server hostname, e.g. smtp.gmail.com.smtp_port — type: integer — optional — default: 587 — range: 1–65535. SMTP port. One of 25, 465, 587. Defaults to 587.smtp_username — type: string — required. SMTP login (usually the email address).smtp_password — type: string — required — format: password. SMTP password or app password.imap_host — type: string — required. IMAP server hostname, e.g. imap.gmail.com.imap_port — type: integer — optional — default: 993 — range: 1–65535. IMAP port. One of 143, 993. Defaults to 993.imap_username — type: string — required. IMAP login (usually the email address).imap_password — type: string — required — format: password. IMAP password or app password.{
"email_address": "[email protected]",
"smtp_host": "smtp.gmail.com",
"smtp_port": 587,
"smtp_username": "[email protected]",
"smtp_password": "app-password",
"imap_host": "imap.gmail.com",
"imap_port": 993,
"imap_username": "[email protected]",
"imap_password": "app-password"
}
{
"data": {
"smtp_ok": true,
"imap_ok": true
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
validation_error — Body failed validation.ssrf_blocked — A host resolved to a private/internal IP.PATCH /api/v1/mailboxes/{id}
Partial update — only the fields you send change. Changing SMTP/IMAP credentials re-runs the connection test. Set external_id or signature to null to clear them.
mailboxes:writeid — in: path — type: string — required. Mailbox id (UUID).display_name — type: string — optional — range: –255. Display name.smtp_host — type: string — optional. SMTP host.smtp_port — type: integer — optional — range: 1–65535. SMTP port (25/465/587).smtp_username — type: string — optional. SMTP login.smtp_password — type: string — optional — format: password. SMTP password.imap_host — type: string — optional. IMAP host.imap_port — type: integer — optional — range: 1–65535. IMAP port (143/993).imap_username — type: string — optional. IMAP login.imap_password — type: string — optional — format: password. IMAP password.sending_enabled — type: boolean — optional. Allow sending.daily_send_limit — type: integer — optional — range: 1–1000. Daily send cap.external_id — type: string — optional — nullable — range: –255. Your reference id, or null to clear.signature — type: string — optional — nullable — range: –65536. HTML/text signature, or null to clear.{
"display_name": "Jordan Lee",
"daily_send_limit": 80
}
{
"data": {
"id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"external_id": "crm-contact-8841",
"email_address": "[email protected]",
"display_name": "Jordan Lee",
"status": "connected",
"health_status": "ok",
"health_reasons": null,
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 62,
"sending_enabled": true,
"daily_send_limit": 80,
"last_tested_at": "2026-06-10T08:31:00.000Z",
"last_test_error": null,
"signature": null,
"created_at": "2026-06-06T10:43:01.357Z",
"updated_at": "2026-06-10T08:31:05.220Z"
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
validation_error — Body failed validation.not_found — Mailbox not found.duplicate_external_id — The new external_id is already in use.ssrf_blocked — A host resolved to a private/internal IP.DELETE /api/v1/mailboxes/{id}
Soft-deletes the mailbox: it stops sending and warmup immediately but its history is preserved so it can be restored. Returns 200 with the mailbox object (status "deleted") — not 204.
mailboxes:writeid — in: path — type: string — required. Mailbox id (UUID).{
"data": {
"id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"external_id": "crm-contact-8841",
"email_address": "[email protected]",
"display_name": "Jordan Lee",
"status": "deleted",
"health_status": "ok",
"health_reasons": null,
"warmup_enabled": false,
"warmup_type": "normal",
"warmup_progress": 62,
"sending_enabled": true,
"daily_send_limit": 50,
"last_tested_at": "2026-06-10T08:31:00.000Z",
"last_test_error": null,
"signature": null,
"created_at": "2026-06-06T10:43:01.357Z",
"updated_at": "2026-06-10T08:31:05.220Z"
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
not_found — Mailbox not found (or already deleted).POST /api/v1/mailboxes/{id}/restore
Brings a soft-deleted mailbox back with its warmup history intact and re-runs the connection test (status returns to "created" → "connected"). Warmup starts disabled; enable it once connected. Blocked if an active mailbox already uses the same email.
mailboxes:writeid — in: path — type: string — required. Mailbox id (UUID).{
"data": {
"id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"external_id": "crm-contact-8841",
"email_address": "[email protected]",
"display_name": "Jordan Lee",
"status": "created",
"health_status": "ok",
"health_reasons": null,
"warmup_enabled": false,
"warmup_type": "normal",
"warmup_progress": 62,
"sending_enabled": true,
"daily_send_limit": 50,
"last_tested_at": "2026-06-10T08:31:00.000Z",
"last_test_error": null,
"signature": null,
"created_at": "2026-06-06T10:43:01.357Z",
"updated_at": "2026-06-10T08:31:05.220Z"
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
not_found — No soft-deleted mailbox with that id.duplicate_email — An active mailbox already uses this email.POST /api/v1/mailboxes/{id}/test
Re-runs the SMTP + IMAP connection test against the stored credentials and updates the mailbox status. Rate limited.
mailboxes:writeid — in: path — type: string — required. Mailbox id (UUID).{
"data": {
"mailbox": {
"id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"external_id": "crm-contact-8841",
"email_address": "[email protected]",
"display_name": "Jordan Lee",
"status": "connected",
"health_status": "ok",
"health_reasons": null,
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 62,
"sending_enabled": true,
"daily_send_limit": 50,
"last_tested_at": "2026-06-10T08:31:00.000Z",
"last_test_error": null,
"signature": null,
"created_at": "2026-06-06T10:43:01.357Z",
"updated_at": "2026-06-10T08:31:05.220Z"
},
"test_result": {
"smtp_ok": true,
"imap_ok": true
}
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
not_found — Mailbox not found.ssrf_blocked — A host resolved to a private/internal IP.GET /api/v1/warmup/stats
Per-mailbox warmup health: progress, today's volume, inbox/spam rate, reputation. Cursor paginated.
warmup:readpagination.next_cursor.limit — in: query — type: integer — optional — default: 50 — range: 1–100. Results per page (1–100).cursor — in: query — type: string — optional. next_cursor from the previous page.mailbox_id — in: query — type: string — optional. One mailbox id, or comma-separated ids (up to 100), to filter.{
"data": [
{
"mailbox_id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"email_address": "[email protected]",
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 62,
"warmup_target_daily_volume": 50,
"effective_daily_limit": 50,
"effective_limit_reason": null,
"sent_today": 18,
"received_today": 17,
"reputation_score": 96,
"inbox_rate": 98,
"spam_rate": 1,
"last_sent_at": "2026-06-10T09:12:00.000Z",
"health_status": "ok",
"health_reasons": null
}
],
"pagination": {
"has_more": false,
"next_cursor": null
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
GET /api/v1/warmup/stats/{mailboxId}
Warmup health for a single mailbox.
warmup:readmailboxId — in: path — type: string — required. Mailbox id (UUID).{
"data": {
"mailbox_id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"email_address": "[email protected]",
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 62,
"warmup_target_daily_volume": 50,
"effective_daily_limit": 50,
"effective_limit_reason": null,
"sent_today": 18,
"received_today": 17,
"reputation_score": 96,
"inbox_rate": 98,
"spam_rate": 1,
"last_sent_at": "2026-06-10T09:12:00.000Z",
"health_status": "ok",
"health_reasons": null
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
not_found — Mailbox not found.PATCH /api/v1/warmup/settings/{mailboxId}
Enable/disable warmup, switch strategy, or set the target daily volume for one mailbox. Enabling requires the mailbox status to be "connected".
warmup:writemailboxId — in: path — type: string — required. Mailbox id (UUID).warmup_enabled — type: boolean — optional. Turn warmup on or off. Enabling requires the mailbox status to be "connected".warmup_type — type: string — optional — one of: new_domain, new_mailbox, dormant, maintenance, recovery, aggressive, normal, conservative. Warmup strategy. Controls ramp speed and target.warmup_target_daily_volume — type: integer — optional — range: 1–100. Target warmup emails per day once fully ramped.{
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_target_daily_volume": 50
}
{
"data": {
"mailbox_id": "5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"email_address": "[email protected]",
"warmup_enabled": true,
"warmup_type": "normal",
"warmup_progress": 62,
"warmup_target_daily_volume": 50,
"effective_daily_limit": 50,
"effective_limit_reason": null,
"sent_today": 18,
"received_today": 17,
"reputation_score": 96,
"inbox_rate": 98,
"spam_rate": 1,
"last_sent_at": "2026-06-10T09:12:00.000Z",
"health_status": "ok",
"health_reasons": null
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
validation_error — Body failed validation.not_found — Mailbox not found.mailbox_not_connected — Tried to enable warmup on a mailbox that is not connected.POST /api/v1/warmup/bulk-update
Applies the same warmup changes to many mailboxes at once. Returns 202 with a job id immediately — poll GET /jobs/{id} for progress. Mailboxes that can't be enabled (not connected) are recorded as per-item errors on the job.
warmup:writemailbox_ids — type: string[] — required — range: 1–500. Mailbox ids to update (1–500).updates — type: object — required. A warmup settings object — same fields as Update warmup settings (warmup_enabled, warmup_type, warmup_target_daily_volume). At least one field required.{
"mailbox_ids": [
"5bd50e20-6b75-4429-9a5a-4a09b46e945a",
"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"
],
"updates": {
"warmup_enabled": true,
"warmup_type": "normal"
}
}
{
"data": {
"job_id": "job_7d2f1a",
"status": "processing",
"total_items": 2
},
"meta": {
"request_id": "req_mq28denhy91fh6",
"idempotency_key": "550e8400-e29b-41d4-a716-446655440000"
}
}
validation_error — Body failed validation or no update fields given.mailboxes_not_found — One or more mailbox ids don't exist (details.missing_mailbox_ids).GET /api/v1/jobs/{id}
Status and progress of an asynchronous job (e.g. a warmup bulk-update). Per-item failures are listed in errors.
jobs:readid — in: path — type: string — required. Job id (UUID).{
"data": {
"id": "job_7d2f1a",
"type": "bulk_warmup_update",
"status": "completed",
"total_items": 2,
"processed_items": 2,
"failed_items": 0,
"errors": [],
"created_at": "2026-06-10T12:00:00.000Z",
"completed_at": "2026-06-10T12:00:04.000Z"
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
not_found — Job not found.GET /api/v1/prospects
Returns prospects in your workspace with cursor pagination.
prospects:readpagination.next_cursor.limit — in: query — type: integer — optional — default: 50 — range: 1–100. Results per page (1–100).cursor — in: query — type: string — optional. next_cursor from the previous page.{
"data": [
{
"id": "f3c4a2d1-9b7e-4c2a-8f10-2b6d5e8a1c44",
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Doe",
"company": "Acme Inc",
"role": "CTO",
"phone": null,
"globalStatus": "active",
"linkedinUrl": "https://www.linkedin.com/in/janedoe",
"customFields": {
"plan": "enterprise"
},
"createdAt": "2026-06-09T14:02:00.000Z",
"updatedAt": "2026-06-09T14:02:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
POST /api/v1/prospects
Creates a prospect. If one with the same email already exists it is merged and returned with alreadyExisted=true (HTTP 200); a brand-new prospect returns 201.
prospects:writeemail — type: string — required — format: email. Prospect email (unique per workspace).firstName — type: string — optional. First name.lastName — type: string — optional. Last name.company — type: string — optional. Company.role — type: string — optional. Job title / role.phone — type: string — optional. Phone number.linkedinUrl — type: string — optional — format: uri. LinkedIn profile URL.customFields — type: object — optional. Arbitrary key/value pairs for personalization.{
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Doe",
"company": "Acme Inc",
"linkedinUrl": "https://www.linkedin.com/in/janedoe",
"customFields": {
"plan": "enterprise"
}
}
{
"data": {
"id": "f3c4a2d1-9b7e-4c2a-8f10-2b6d5e8a1c44",
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Doe",
"company": "Acme Inc",
"role": "CTO",
"phone": null,
"globalStatus": "active",
"linkedinUrl": "https://www.linkedin.com/in/janedoe",
"customFields": {
"plan": "enterprise"
},
"createdAt": "2026-06-09T14:02:00.000Z",
"updatedAt": "2026-06-09T14:02:00.000Z"
},
"alreadyExisted": false
}
validation_error — Body failed validation.prospect_limit_reached — Workspace prospect limit reached.PATCH /api/v1/prospects/{id}
Partial update of a prospect. linkedinUrl may be set or cleared (null). globalStatus can be changed (e.g. to mark unsubscribed).
prospects:writeid — in: path — type: string — required. Prospect id (UUID).firstName — type: string — optional. First name.lastName — type: string — optional. Last name.company — type: string — optional. Company.role — type: string — optional. Role.phone — type: string — optional. Phone.linkedinUrl — type: string — optional — format: uri — nullable. LinkedIn URL, or null to clear.customFields — type: object — optional. Custom fields (replaces existing).globalStatus — type: string — optional — one of: active, bounced, invalid, unsubscribed, dnc, suppressed. Lifecycle status.{
"role": "VP Engineering",
"globalStatus": "active"
}
{
"data": {
"id": "f3c4a2d1-9b7e-4c2a-8f10-2b6d5e8a1c44",
"email": "[email protected]",
"firstName": "Jane",
"lastName": "Doe",
"company": "Acme Inc",
"role": "VP Engineering",
"phone": null,
"globalStatus": "active",
"linkedinUrl": "https://www.linkedin.com/in/janedoe",
"customFields": {
"plan": "enterprise"
},
"createdAt": "2026-06-09T14:02:00.000Z",
"updatedAt": "2026-06-09T14:02:00.000Z"
}
}
validation_error — Body failed validation.not_found — Prospect not found.POST /api/v1/prospects/suppress
Adds emails to your suppression list so those people are never contacted by any campaign. Existing prospects are marked suppressed.
suppressions:writeemails — type: string[] — required — range: 1–1000. Emails to suppress (1–1000).reason — type: string — optional. Optional reason stored with each entry.{
"emails": [
"[email protected]",
"[email protected]"
],
"reason": "manual opt-out"
}
{
"data": {
"suppressedCount": 2,
"alreadySuppressedCount": 0,
"notFoundCount": 0,
"totalProcessed": 2
}
}
validation_error — Body failed validation.GET /api/v1/suppressions
Returns suppressed emails and domains (the blocklist) with cursor pagination.
suppressions:readpagination.next_cursor.limit — in: query — type: integer — optional — default: 50 — range: 1–100. Results per page (1–100).cursor — in: query — type: string — optional. next_cursor from the previous page.type — in: query — type: string — optional — one of: email, domain. Filter by entry type.{
"data": [
{
"id": "sup_1a2b3c",
"type": "email",
"value": "[email protected]",
"source": "api",
"reason": "manual opt-out",
"createdAt": "2026-06-10T12:00:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
GET /api/v1/campaigns
Returns campaigns with summary stats (sent/opened/replied/...). Cursor paginated.
campaigns:readpagination.next_cursor.limit — in: query — type: integer — optional — default: 50 — range: 1–100. Results per page (1–100).cursor — in: query — type: string — optional. next_cursor from the previous page.status — in: query — type: string — optional — one of: draft, scheduled, running, paused, completed, error. Filter by status.{
"data": [
{
"id": "c1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"name": "Q3 Outbound — Founders",
"status": "draft",
"channel": "email",
"description": null,
"timezone": "UTC",
"dailySendLimit": 50,
"sendingWindowStart": null,
"sendingWindowEnd": null,
"scheduleDays": null,
"stopOnReply": true,
"stopOnBounce": true,
"stopOnUnsubscribe": true,
"trackOpens": true,
"trackClicks": true,
"totalProspects": 0,
"sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"bounced": 0,
"unsubscribed": 0,
"startDate": null,
"endDate": null,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:00:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
GET /api/v1/campaigns/{id}
Returns a single campaign including its ordered steps array.
campaigns:readid — in: path — type: string — required. Campaign id (UUID).{
"data": {
"id": "c1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"name": "Q3 Outbound — Founders",
"status": "draft",
"channel": "email",
"description": null,
"timezone": "UTC",
"dailySendLimit": 50,
"sendingWindowStart": null,
"sendingWindowEnd": null,
"scheduleDays": null,
"stopOnReply": true,
"stopOnBounce": true,
"stopOnUnsubscribe": true,
"trackOpens": true,
"trackClicks": true,
"totalProspects": 0,
"sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"bounced": 0,
"unsubscribed": 0,
"startDate": null,
"endDate": null,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:00:00.000Z",
"steps": [
{
"id": "s1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"stepIndex": 0,
"type": "email",
"name": "Intro",
"subject": "Quick question, {{firstName}}",
"bodyHtml": "<p>Hi {{firstName}}, ...</p>",
"bodyText": "Hi {{firstName}}, ...",
"delayDays": 0,
"delayHours": null,
"isActive": true,
"sendAsReply": null,
"sentCount": 0,
"openCount": 0,
"clickCount": 0,
"replyCount": 0,
"bounceCount": 0,
"linkedinActionType": null,
"linkedinMessageTemplate": null,
"linkedinInviteNote": null,
"linkedinSubject": null,
"linkedinMaxWaitDays": null,
"linkedinEngagementConfig": null,
"linkedinInmailConfig": null
}
]
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
not_found — Campaign not found.POST /api/v1/campaigns
Creates a campaign in draft status with one or more steps, assigned mailboxes, and (optionally) enrolled prospects. Start it later with the start endpoint.
campaigns:writename — type: string — required — range: 1–200. Campaign name.description — type: string — optional — range: –2000. Optional description.channel — type: string — optional — one of: email, linkedin, multichannel — default: email. Campaign channel.linkedinAccountId — type: string — optional — format: uuid. LinkedIn account id (for linkedin/multichannel).timezone — type: string — optional — default: UTC — range: –100. IANA timezone for the sending schedule.dailySendLimit — type: integer — optional — default: 50 — range: 1–10000. Daily send cap for the campaign.sendingWindowStart — type: integer — optional — range: 0–23. Start hour (0–23) of the daily sending window.sendingWindowEnd — type: integer — optional — range: 0–23. End hour (0–23) of the daily sending window.scheduleDays — type: integer[] — optional. Days of week to send (0=Sun … 6=Sat).stopOnReply — type: boolean — optional — default: true. Stop a prospect's sequence when they reply.stopOnBounce — type: boolean — optional — default: true. Stop on bounce.stopOnUnsubscribe — type: boolean — optional — default: true. Stop on unsubscribe.trackOpens — type: boolean — optional — default: true. Track opens.trackClicks — type: boolean — optional — default: true. Track clicks.startDate — type: string — optional — format: date-time. Optional scheduled start.endDate — type: string — optional — format: date-time. Optional end date.steps — type: object[] — required — range: 1–50. Ordered steps. Each: { stepIndex, type (email|linkedin|condition), subject, bodyHtml, bodyText, delayDays, delayHours, isActive, … }.mailboxIds — type: string[] — required — range: –100. Mailbox ids that send this campaign.prospectIds — type: string[] — optional — range: –10000. Prospect ids to enroll on creation.{
"name": "Q3 Outbound — Founders",
"channel": "email",
"dailySendLimit": 50,
"mailboxIds": [
"5bd50e20-6b75-4429-9a5a-4a09b46e945a"
],
"steps": [
{
"stepIndex": 0,
"type": "email",
"subject": "Quick question, {{firstName}}",
"bodyHtml": "<p>Hi {{firstName}}, ...</p>",
"delayDays": 0
},
{
"stepIndex": 1,
"type": "email",
"subject": "Re: Quick question",
"bodyHtml": "<p>Following up ...</p>",
"delayDays": 3,
"sendAsReply": true
}
],
"prospectIds": [
"f3c4a2d1-9b7e-4c2a-8f10-2b6d5e8a1c44"
]
}
{
"data": {
"id": "c1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"name": "Q3 Outbound — Founders",
"status": "draft",
"channel": "email",
"description": null,
"timezone": "UTC",
"dailySendLimit": 50,
"sendingWindowStart": null,
"sendingWindowEnd": null,
"scheduleDays": null,
"stopOnReply": true,
"stopOnBounce": true,
"stopOnUnsubscribe": true,
"trackOpens": true,
"trackClicks": true,
"totalProspects": 0,
"sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"bounced": 0,
"unsubscribed": 0,
"startDate": null,
"endDate": null,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:00:00.000Z",
"steps": [
{
"id": "s1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"stepIndex": 0,
"type": "email",
"name": "Intro",
"subject": "Quick question, {{firstName}}",
"bodyHtml": "<p>Hi {{firstName}}, ...</p>",
"bodyText": "Hi {{firstName}}, ...",
"delayDays": 0,
"delayHours": null,
"isActive": true,
"sendAsReply": null,
"sentCount": 0,
"openCount": 0,
"clickCount": 0,
"replyCount": 0,
"bounceCount": 0,
"linkedinActionType": null,
"linkedinMessageTemplate": null,
"linkedinInviteNote": null,
"linkedinSubject": null,
"linkedinMaxWaitDays": null,
"linkedinEngagementConfig": null,
"linkedinInmailConfig": null
}
]
},
"meta": {
"prospects_enrolled": 1,
"prospects_suppressed": 0,
"mailboxes_assigned": 1
}
}
validation_error — Body / step validation failed.subscription_blocked — Your plan doesn't allow this action.PATCH /api/v1/campaigns/{id}
Updates campaign settings. Only allowed while the campaign is in draft or paused status.
campaigns:writeid — in: path — type: string — required. Campaign id (UUID).name — type: string — optional — range: 1–200. Campaign name.description — type: string — optional — range: –2000. Description.dailySendLimit — type: integer — optional — range: 1–10000. Daily send cap.sendingWindowStart — type: integer — optional — range: 0–23. Window start hour.sendingWindowEnd — type: integer — optional — range: 0–23. Window end hour.scheduleDays — type: integer[] — optional. Days of week (0–6).stopOnReply — type: boolean — optional. Stop on reply.stopOnBounce — type: boolean — optional. Stop on bounce.stopOnUnsubscribe — type: boolean — optional. Stop on unsubscribe.trackOpens — type: boolean — optional. Track opens.trackClicks — type: boolean — optional. Track clicks.startDate — type: string — optional — format: date-time — nullable. Scheduled start, or null.endDate — type: string — optional — format: date-time — nullable. End date, or null.{
"dailySendLimit": 80,
"scheduleDays": [
1,
2,
3,
4,
5
]
}
{
"data": {
"id": "c1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"name": "Q3 Outbound — Founders",
"status": "draft",
"channel": "email",
"description": null,
"timezone": "UTC",
"dailySendLimit": 80,
"sendingWindowStart": null,
"sendingWindowEnd": null,
"scheduleDays": [
1,
2,
3,
4,
5
],
"stopOnReply": true,
"stopOnBounce": true,
"stopOnUnsubscribe": true,
"trackOpens": true,
"trackClicks": true,
"totalProspects": 0,
"sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"bounced": 0,
"unsubscribed": 0,
"startDate": null,
"endDate": null,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:00:00.000Z",
"steps": [
{
"id": "s1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"stepIndex": 0,
"type": "email",
"name": "Intro",
"subject": "Quick question, {{firstName}}",
"bodyHtml": "<p>Hi {{firstName}}, ...</p>",
"bodyText": "Hi {{firstName}}, ...",
"delayDays": 0,
"delayHours": null,
"isActive": true,
"sendAsReply": null,
"sentCount": 0,
"openCount": 0,
"clickCount": 0,
"replyCount": 0,
"bounceCount": 0,
"linkedinActionType": null,
"linkedinMessageTemplate": null,
"linkedinInviteNote": null,
"linkedinSubject": null,
"linkedinMaxWaitDays": null,
"linkedinEngagementConfig": null,
"linkedinInmailConfig": null
}
]
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
invalid_status — Campaign isn't in draft/paused status.not_found — Campaign not found.POST /api/v1/campaigns/{id}/start
Moves a draft/paused campaign into running. No request body.
campaigns:writeid — in: path — type: string — required. Campaign id (UUID).{
"data": {
"id": "c1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"name": "Q3 Outbound — Founders",
"status": "running",
"channel": "email",
"description": null,
"timezone": "UTC",
"dailySendLimit": 50,
"sendingWindowStart": null,
"sendingWindowEnd": null,
"scheduleDays": null,
"stopOnReply": true,
"stopOnBounce": true,
"stopOnUnsubscribe": true,
"trackOpens": true,
"trackClicks": true,
"totalProspects": 0,
"sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"bounced": 0,
"unsubscribed": 0,
"startDate": null,
"endDate": null,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:00:00.000Z",
"steps": [
{
"id": "s1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"stepIndex": 0,
"type": "email",
"name": "Intro",
"subject": "Quick question, {{firstName}}",
"bodyHtml": "<p>Hi {{firstName}}, ...</p>",
"bodyText": "Hi {{firstName}}, ...",
"delayDays": 0,
"delayHours": null,
"isActive": true,
"sendAsReply": null,
"sentCount": 0,
"openCount": 0,
"clickCount": 0,
"replyCount": 0,
"bounceCount": 0,
"linkedinActionType": null,
"linkedinMessageTemplate": null,
"linkedinInviteNote": null,
"linkedinSubject": null,
"linkedinMaxWaitDays": null,
"linkedinEngagementConfig": null,
"linkedinInmailConfig": null
}
]
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
invalid_status — Campaign can't be started from its current status.subscription_blocked — Your plan doesn't allow this action.not_found — Campaign not found.POST /api/v1/campaigns/{id}/pause
Pauses a running campaign. No request body.
campaigns:writeid — in: path — type: string — required. Campaign id (UUID).{
"data": {
"id": "c1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"name": "Q3 Outbound — Founders",
"status": "paused",
"channel": "email",
"description": null,
"timezone": "UTC",
"dailySendLimit": 50,
"sendingWindowStart": null,
"sendingWindowEnd": null,
"scheduleDays": null,
"stopOnReply": true,
"stopOnBounce": true,
"stopOnUnsubscribe": true,
"trackOpens": true,
"trackClicks": true,
"totalProspects": 0,
"sent": 0,
"opened": 0,
"clicked": 0,
"replied": 0,
"bounced": 0,
"unsubscribed": 0,
"startDate": null,
"endDate": null,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:00:00.000Z",
"steps": [
{
"id": "s1a2b3c4-d5e6-47f8-9a0b-1c2d3e4f5a6b",
"stepIndex": 0,
"type": "email",
"name": "Intro",
"subject": "Quick question, {{firstName}}",
"bodyHtml": "<p>Hi {{firstName}}, ...</p>",
"bodyText": "Hi {{firstName}}, ...",
"delayDays": 0,
"delayHours": null,
"isActive": true,
"sendAsReply": null,
"sentCount": 0,
"openCount": 0,
"clickCount": 0,
"replyCount": 0,
"bounceCount": 0,
"linkedinActionType": null,
"linkedinMessageTemplate": null,
"linkedinInviteNote": null,
"linkedinSubject": null,
"linkedinMaxWaitDays": null,
"linkedinEngagementConfig": null,
"linkedinInmailConfig": null
}
]
},
"meta": {
"request_id": "req_mq28denhy91fh6"
}
}
invalid_status — Campaign isn't running.not_found — Campaign not found.POST /api/v1/campaigns/{id}/enrollments
Adds prospects to a campaign by prospect id, audience list id, and/or email. Suppressed prospects are skipped and counted separately. Provide at least one of prospectIds, listIds, emails.
enrollments:writeid — in: path — type: string — required. Campaign id (UUID).prospectIds — type: string[] — optional. Prospect ids to enroll.listIds — type: string[] — optional. Audience list ids — every prospect on each list is enrolled.emails — type: string[] — optional. Prospect emails to enroll.{
"emails": [
"[email protected]"
],
"listIds": [
"list_9f3a2b"
]
}
{
"data": {
"enrolledCount": 12,
"suppressedCount": 1,
"invalidCount": 0,
"alreadyEnrolledCount": 2,
"totalProcessed": 15
}
}
validation_error — No targets given or body invalid.not_found — Campaign not found.DELETE /api/v1/campaigns/{id}/enrollments
Removes prospects from a campaign (soft-stop — their audit history is preserved and any pending sends are cancelled). Provide at least one of prospectIds, emails.
enrollments:writeid — in: path — type: string — required. Campaign id (UUID).prospectIds — type: string[] — optional. Prospect ids to unenroll.emails — type: string[] — optional. Prospect emails to unenroll.{
"emails": [
"[email protected]"
]
}
{
"data": {
"unenrolledCount": 1,
"totalProcessed": 1
}
}
validation_error — No targets given or body invalid.not_found — Campaign not found.GET /api/v1/webhooks
Returns your configured webhook endpoints (without secrets).
webhooks:read{
"data": [
{
"id": "wh_2c1a9f",
"url": "https://example.com/hooks/warmysender",
"events": [
"reply.received",
"email.bounced"
],
"isActive": true,
"lastTriggeredAt": null,
"failureCount": 0,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:00:00.000Z"
}
]
}
POST /api/v1/webhooks
Registers a webhook endpoint for one or more event types. The signing secret (whsec_…) is returned ONCE in this response — store it; you'll use it to verify payload signatures.
webhooks:writeurl — type: string — required — format: uri — range: –500. HTTPS URL that receives POST deliveries.events — type: string[] — required — range: 1–. Event types to subscribe to (see Webhook Events).{
"url": "https://example.com/hooks/warmysender",
"events": [
"reply.received",
"email.bounced",
"linkedin.reply_received"
]
}
{
"data": {
"id": "wh_2c1a9f",
"url": "https://example.com/hooks/warmysender",
"events": [
"reply.received",
"email.bounced",
"linkedin.reply_received"
],
"isActive": true,
"secret": "whsec_3f9a...redacted",
"createdAt": "2026-06-10T12:00:00.000Z"
},
"message": "Webhook created. Save the secret - it will not be shown again."
}
validation_error — Invalid URL or unknown event type.PATCH /api/v1/webhooks/{id}
Change a webhook's URL, subscribed events, or active flag.
webhooks:writeid — in: path — type: string — required. Webhook id.url — type: string — optional — format: uri — range: –500. New delivery URL.events — type: string[] — optional — range: 1–. Replacement list of event types.isActive — type: boolean — optional. Enable or disable deliveries.{
"isActive": false
}
{
"data": {
"id": "wh_2c1a9f",
"url": "https://example.com/hooks/warmysender",
"events": [
"reply.received"
],
"isActive": false,
"lastTriggeredAt": null,
"failureCount": 0,
"createdAt": "2026-06-10T12:00:00.000Z",
"updatedAt": "2026-06-10T12:05:00.000Z"
}
}
validation_error — Body failed validation.not_found — Webhook not found.DELETE /api/v1/webhooks/{id}
Permanently removes a webhook endpoint. Returns 204 with no body.
webhooks:writeid — in: path — type: string — required. Webhook id.No response body.
not_found — Webhook not found.POST /api/v1/webhooks/{id}/test
Queues a webhook.test delivery to the endpoint so you can verify signature handling end-to-end. No request body.
webhooks:writeid — in: path — type: string — required. Webhook id.{
"data": {
"eventId": "evt_a1b2c3",
"status": "queued",
"message": "Test webhook event has been queued for delivery"
}
}
not_found — Webhook not found.GET /api/v1/linkedin/accounts
Returns connected LinkedIn accounts with their daily engagement limits and how many actions remain today.
linkedin:readlimit — in: query — type: integer — optional — default: 50 — range: 1–100. Results to return (1–100).{
"data": [
{
"id": "li_5a2b1c",
"name": "Jordan Lee",
"linkedin_url": "https://www.linkedin.com/in/jordanlee",
"status": "connected",
"linkedin_account_type": "classic",
"strategy": "steady",
"ramp_week": 3,
"engagements_today": 4,
"engagements_day": "2026-06-10",
"created_at": "2026-05-20T10:00:00.000Z",
"daily_limits": {
"engagements_per_day": 30,
"engagements_remaining": 26
}
}
]
}
POST /api/v1/linkedin/posts/{postId}/reactions
Adds a reaction (like, celebrate, etc.) to a LinkedIn post from one of your connected accounts. Subject to per-account daily engagement limits — account safety always wins.
linkedin:writepostId — in: path — type: string — required. LinkedIn post id (URN).linkedin_account_id — type: string — required. Which connected LinkedIn account reacts.reaction_type — type: string — optional — one of: LIKE, CELEBRATE, SUPPORT, LOVE, INSIGHTFUL, FUNNY — default: LIKE. Reaction (case-insensitive; defaults to LIKE).{
"linkedin_account_id": "li_5a2b1c",
"reaction_type": "LIKE"
}
{
"data": {
"post_id": "urn:li:activity:7300000000000000000",
"reaction_type": "LIKE",
"linkedin_account_id": "li_5a2b1c"
}
}
account_not_connected — The LinkedIn account isn't connected.account_unauthorized — The LinkedIn session needs re-authentication.daily_limit_reached — The account hit its daily engagement limit.GET /api/v1/linkedin/posts/{postId}/reactions
Lists who reacted to a post, as seen by one of your connected accounts. Cursor paginated.
linkedin:readpagination.next_cursor.postId — in: path — type: string — required. LinkedIn post id (URN).linkedin_account_id — in: query — type: string — required. Which connected account performs the read.limit — in: query — type: integer — optional — default: 20 — range: 1–100. Results per page (1–100).cursor — in: query — type: string — optional. next_cursor from the previous page.{
"data": [
{
"user_id": "urn:li:person:abc",
"name": "Sam Rivera",
"reaction_type": "LIKE",
"timestamp": "2026-06-10T11:00:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
}
account_not_connected — The LinkedIn account isn't connected.account_unauthorized — The LinkedIn session needs re-authentication.WarmySender posts real-time webhook notifications for key events. Configure them from Settings → Webhooks or via the API. Each delivery is signed with HMAC-SHA256 (X-Warmy-Signature) and retried with exponential backoff until your endpoint returns a 2xx. Subscribe to any of the event types below.
reply.receivedA prospect replied (email or LinkedIn).
{
"prospectId": "f3c4a2d1-...",
"prospectEmail": "[email protected]",
"campaignId": "c1a2b3c4-...",
"mailboxId": "5bd50e20-...",
"subject": "Re: Quick question",
"messageId": "<...>",
"receivedAt": "2026-06-10T11:00:00.000Z",
"channel": "email"
}
email.bouncedAn outbound email bounced (hard or soft).
{
"prospectEmail": "[email protected]",
"campaignId": "c1a2b3c4-...",
"mailboxId": "5bd50e20-...",
"bounceType": "hard",
"bounceReason": "550 user unknown",
"bouncedAt": "2026-06-10T11:01:00.000Z"
}
email.unsubscribedA prospect unsubscribed.
{
"prospectId": "f3c4a2d1-...",
"prospectEmail": "[email protected]",
"campaignId": "c1a2b3c4-...",
"unsubscribedAt": "2026-06-10T11:02:00.000Z"
}
email.openedA tracked email was opened.
{
"prospectId": "f3c4a2d1-...",
"prospectEmail": "[email protected]",
"campaignId": "c1a2b3c4-...",
"stepIndex": 0,
"openedAt": "2026-06-10T11:03:00.000Z"
}
email.clickedA tracked link was clicked.
{
"prospectId": "f3c4a2d1-...",
"prospectEmail": "[email protected]",
"campaignId": "c1a2b3c4-...",
"url": "https://yourdomain.com/demo",
"clickedAt": "2026-06-10T11:04:00.000Z"
}
prospect.suppressedA prospect was added to the suppression list.
{
"prospectId": "f3c4a2d1-...",
"email": "[email protected]",
"reason": "manual opt-out",
"suppressedAt": "2026-06-10T11:05:00.000Z"
}
limit.hitA sending limit (mailbox/domain/prospect/cooldown) was reached.
{
"limitType": "daily_mailbox",
"mailboxId": "5bd50e20-...",
"currentValue": 50,
"limitValue": 50,
"hitAt": "2026-06-10T11:06:00.000Z"
}
linkedin.invite_acceptedA LinkedIn connection invite was accepted.
{
"prospectName": "Sam Rivera",
"prospectLinkedinUrl": "https://www.linkedin.com/in/samrivera",
"campaignId": "c1a2b3c4-...",
"enrollmentId": "enr_...",
"linkedinAccountId": "li_5a2b1c",
"acceptedAt": "2026-06-10T11:07:00.000Z"
}
linkedin.reply_receivedA LinkedIn message reply was received.
{
"prospectName": "Sam Rivera",
"prospectLinkedinUrl": "https://www.linkedin.com/in/samrivera",
"campaignId": "c1a2b3c4-...",
"enrollmentId": "enr_...",
"linkedinAccountId": "li_5a2b1c",
"messageText": "Sure, let's talk",
"threadId": "th_...",
"receivedAt": "2026-06-10T11:08:00.000Z"
}
webhook.testA manual test event sent from the test endpoint.
{
"webhookId": "wh_2c1a9f",
"timestamp": "2026-06-10T11:09:00.000Z",
"message": "This is a test event from WarmySender"
}
Errors return a JSON body with a stable code and a human-readable message. These apply across all endpoints (individual endpoints list their own additional errors above).
missing_idempotency_key — An Idempotency-Key header is required for this request.validation_error — The request body or parameters failed validation.unauthorized — Missing or invalid API key.insufficient_scope — The API key lacks the scope this endpoint requires.not_found — The resource does not exist (or you have no access to it).idempotency_conflict — The Idempotency-Key was reused with a different request body.ssrf_blocked — A supplied host resolved to a private/internal IP and was blocked.rate_limited — Too many requests — slow down and retry after the Retry-After delay.internal_error — Something went wrong on our side.Paste a Slack Incoming Webhook URL (https://hooks.slack.com/services/…) into Settings → Webhooks and we auto-format each event as a Block Kit message. LinkedIn reply notifications include the reply body, prospect name, profile URL, and campaign — rendered as a readable Slack card. No Zapier, Make, or n8n required in the middle. Slack rate limits are honored automatically via the Retry-After header Slack returns when throttling.
Create an HTTP Trigger (Webhook) node in n8n (or a Webhook trigger in Zapier / Make), copy its Production URL, and paste it into Settings → Webhooks. You receive the full JSON payload and can fan out to any downstream system — Slack, Discord, Teams, HubSpot, Pipedrive, Airtable, Google Sheets, Notion, or a custom database — with conditional logic and transformations between steps. Signature verification via X-Warmy-Signature is recommended when your endpoint is publicly reachable.