Less boo. More booked. Halloween offer: 30 days free on any paid plan. Claim 30 days freeEvery new account gets one free mailbox for warmup and replies. No domain to buy. See how it works

Skip to content

Webhooks

API reference6 min read7 sections

Subscribe a URL you control to receive HTTP POST notifications for mailbox health events, health score drops, auto-pauses, OAuth disconnects, DNS changes, and similar. This page covers managing outbound webhook subscriptions; it's unrelated to Warmerly's own inbound provider webhooks (Stripe, Unipile), which aren't part of the public API.

Webhook subscriptions are an Agency-plan feature. Listing, creating, and updating a subscription return 403 agency_plan_required on any workspace that is not on the Agency plan. The delete route itself has no plan check, but an API key is only accepted while the workspace the request acts on is on the Agency plan, so a key stops working for every endpoint after a downgrade. Deliveries also pause while the owner is not on the Agency plan, without counting as failures.

All endpoints below require X-Api-Key and are not project-scoped, a subscription receives events for every mailbox in the workspaces you own, across every project (workspaces that have been deleted are skipped).

API reference

#Event types

Pass one or more of these in eventTypes when creating a subscription:

Event typeDescription
score_dropA mailbox's warmup health score dropped.
score_floorA mailbox's health score hit a critical floor.
oauth_disconnectedA sign-in connected mailbox (Microsoft 365, Outlook, or Gmail connected before 2026-09-15) lost its connection.
auth_failA mailbox failed to authenticate.
dns_changedA mailbox's DNS records (SPF/DKIM/DMARC) changed.
smtp_failAn SMTP send failed for a mailbox.
imap_failAn IMAP sync failed for a mailbox.
flaggedA mailbox was flagged.
auto_pausedA mailbox was automatically paused.
auto_resumedA mailbox was automatically resumed after a pause.
manual_pauseA mailbox was manually paused from the dashboard.
aged_inA mailbox finished warmup ramp-up and is now fully aged in.
marked_deadA mailbox was marked dead.
oauth_refresh_failAn OAuth token refresh failed for a mailbox.
oauth_connectedA mailbox was connected via OAuth.
oauth_reconnectedA previously disconnected OAuth mailbox was reconnected.
API reference

#List subscriptions

text
GET /webhooks/subscriptions

Returns your active (non-revoked) subscriptions. The secret is masked to its last 4 characters, it's only shown in full at creation time.

bash
curl https://app.warmerly.com/api/v1/webhooks/subscriptions \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
json
{
  "subscriptions": [
    {
      "id": "e5b3a7c2-8d4e-4f0a-9c2b-6a1d3e8f5c7b",
      "name": "Mailbox alerts → Slack relay",
      "targetUrl": "https://example.com/hooks/warmerly",
      "secret": "••••7c9e",
      "eventTypes": ["score_drop", "oauth_disconnected"],
      "active": true,
      "lastDispatchedAt": "2026-08-18T09:00:00.000Z",
      "lastDeliveryStatus": "success",
      "lastDeliveryError": null,
      "consecutiveFailures": 0,
      "createdAt": "2026-07-01T10:00:00.000Z"
    }
  ]
}
API reference

#Create a subscription

text
POST /webhooks/subscriptions
FieldTypeRequiredDescription
namestring (1-120 chars)YesA label to identify the subscription.
targetUrlstring (URL)YesMust be a public https:// URL. Non-https URLs, URLs with a username or password, localhost, .local and .internal names, and private, link-local or reserved IP addresses are rejected. The hostname is checked again at delivery time.
eventTypesstring[]YesOne or more values from Event types, at most 16 entries.

Each user can hold at most 10 subscriptions that have not been deleted (disabled ones count); creating an 11th returns 400 too_many_subscriptions.

bash
curl -X POST https://app.warmerly.com/api/v1/webhooks/subscriptions \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mailbox alerts",
    "targetUrl": "https://example.com/hooks/warmerly",
    "eventTypes": ["score_drop", "auto_paused", "oauth_disconnected"]
  }'

201 Created:

json
{
  "subscription": {
    "id": "e5b3a7c2-8d4e-4f0a-9c2b-6a1d3e8f5c7b",
    "name": "Mailbox alerts",
    "targetUrl": "https://example.com/hooks/warmerly",
    "eventTypes": ["score_drop", "auto_paused", "oauth_disconnected"]
  },
  "secret": "8f2a7c9e4b1d6f3a9c2e5b8d1a4f7c9e6b3d8a1f4c7e9b2d5a8f1c4e7b9d3a6f"
}

secret is only ever returned in full here. Store it, as it's used to verify the X-Warmerly-Signature header on every delivery (see Verifying deliveries below). It cannot be retrieved again; delete the subscription and create a new one if you lose it.

Errors: 400 bad_request (invalid_body for a malformed request, or unknown_event_types with details: { unknown: [...] } if eventTypes contains a value not in the table above), 403 agency_plan_required if your plan doesn't include webhooks.

API reference

#Update a subscription

text
PATCH /webhooks/subscriptions/{id}
FieldTypeRequiredDescription
activebooleanYesEnable or disable delivery. Any update also resets consecutiveFailures to 0 and clears lastDeliveryError.

Currently the only supported update is toggling active, there's no way to change targetUrl or eventTypes in place; delete and recreate the subscription instead. This endpoint is also how you manually re-enable a subscription the delivery sweep auto-disabled (see Delivery & retries).

bash
curl -X PATCH https://app.warmerly.com/api/v1/webhooks/subscriptions/e5b3a7c2-8d4e-4f0a-9c2b-6a1d3e8f5c7b \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "active": true }'
json
{
  "subscription": {
    "id": "e5b3a7c2-8d4e-4f0a-9c2b-6a1d3e8f5c7b",
    "name": "Mailbox alerts",
    "targetUrl": "https://example.com/hooks/warmerly",
    "eventTypes": ["score_drop", "auto_paused", "oauth_disconnected"],
    "active": true
  }
}

400 bad_request (invalid_body) if active is missing or not a boolean, 403 agency_plan_required on a non-Agency workspace, and 404 not_found if the subscription doesn't exist, isn't yours, or was deleted.

API reference

#Delete a subscription

text
DELETE /webhooks/subscriptions/{id}

Revokes (soft-deletes) the subscription; deliveries stop immediately. Not gated on plan tier, a downgraded workspace can still turn off deliveries it can no longer manage.

bash
curl -X DELETE https://app.warmerly.com/api/v1/webhooks/subscriptions/e5b3a7c2-8d4e-4f0a-9c2b-6a1d3e8f5c7b \
  -H "X-Api-Key: wmv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
json
{ "revoked": true }

404 not_found if the subscription doesn't exist, isn't yours, or was already revoked.

API reference

#Delivery & retries

Deliveries are dispatched by a background sweep (not real-time) that polls for new events every 5 minutes. Each delivery is a POST to your targetUrl, with content-type: application/json and the X-Warmerly-Signature header:

json
{
  "id": "9d1e0b7a-5e7b-4f10-b2aa-1c8f0d3e6a4b",
  "type": "score_drop",
  "accountId": "3a6f9e2e-4b7a-4e9a-9b1d-6e3a2f8c1a10",
  "occurredAt": "2026-08-18T09:00:00.000Z",
  "data": { "severity": "warning", "title": "Health score dropped", "body": "...", "scoreBefore": 88, "scoreAfter": 61 }
}

id is the ID of the underlying event and occurredAt is when it was recorded. data's shape varies by type. For score_drop, score_floor, oauth_disconnected, auth_fail and dns_changed it is { severity, title, body, scoreBefore, scoreAfter } (severity is info, warning or critical; the score fields can be null). For the other event types it is the stored event payload as-is, which is not separately documented per type (see Known gaps).

  • Each delivery is tried up to 3 times (0.5 s, then 1 s, between attempts). An attempt fails if your endpoint doesn't respond 2xx within 5 seconds, or errors.
  • Events are delivered in order. An event that fails all 3 attempts is not redelivered in a later sweep: the subscription moves on past it.
  • A subscription is automatically disabled (active: false) after 10 consecutive sweeps in which a delivery failed. Re-enable it with PATCH .../{id}.
  • Up to 50 events per subscription are delivered per sweep; if you have a burst larger than that, the remainder is delivered on the next sweep.
  • Redirects are never followed; a 3xx response counts as a failure, so respond 2xx directly from targetUrl.
  • lastDeliveryStatus is success or failed. On failure lastDeliveryError is one of http_<status>, timeout, connection_failed, unsafe_target_url or dns_resolution_failed.

#Verifying deliveries

Every delivery carries an X-Warmerly-Signature header: the hex-encoded HMAC-SHA256 of the raw request body, signed with your subscription's secret.

text
X-Warmerly-Signature: 3f9a7c2e...  (hex-encoded HMAC-SHA256 of the raw body)

Verify it by recomputing the HMAC over the exact bytes of the received body and comparing:

js
const crypto = require("crypto");
const expected = crypto.createHmac("sha256", subscriptionSecret).update(rawBody).digest("hex");
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature));
API reference

#Known gaps in this page

  • The exact data payload shape is not documented for the 11 account event types (smtp_fail through oauth_reconnected); it is the stored event payload as-is.
  • There's no endpoint to list recent delivery attempts/history beyond the single lastDispatchedAt/lastDeliveryStatus/lastDeliveryError snapshot on the subscription itself.
Was this page helpful?