Skip to main content
WebhookEmail

Webhook signing documentation corrected

Webhook Signature Verification said webhooks are signed with your Account API Key. They are not: since May 2026 a webhook request is signed only when the webhook has its own secret, and a webhook without one is delivered without X-SendPost-Signature.
  • Set a secret with secret on Create Webhook or Update Webhook, or in the dashboard when you create the webhook. Then verify with that secret wherever you used the API key; the verification code is otherwise unchanged.
  • secret is now listed in the webhook request schema. The API already accepted it.
  • A per-email webhookEndpoint receives the email’s events in addition to your account webhooks, not instead of them. Requests to it are signed only when its URL exactly matches an account webhook that has a secret. See Per-Email Webhook Endpoints.
No API behavior changed with this update.
WebhookSub-Account

Webhook create and update fixes

  • Create Webhook no longer fails with an empty HTTP 500 when the body has no eventSubOptions.
  • Fields you leave out on create now take their documented defaults: enabled and most events are on. Send false for anything you don’t want. The SDKs already sent these defaults, so SDK integrations are unaffected.
  • Update Webhook now changes only the fields you send. Before, every field you left out was switched off and the URL was cleared.
  • url must be an absolute http:// or https:// URL. Anything else returns HTTP 422 with {"error": "<reason>"}, and the other webhook errors now carry the same body.
  • The sub-account apiKey is returned only when you create a sub-account or regenerate its key. Listing or getting sub-accounts doesn’t include it.
Integrations that send every field get the same result as before.
WebhookEmail

Real-time validation results on webhooks

Sub-accounts with the Real-Time List Cleaning add-on can now receive the result of each email’s address check as a Validation webhook event (type 82). See Real-Time Validation Results.
  • event.validationStatus is valid, invalid, catch all or unknown. invalid emails are not sent.
  • Turn it on with validated: true on an account webhook, or the Validation checkbox in the dashboard. Filter results with eventSubOptions.validated.
  • Skip the check for one email with skipValidation: true on Send Email and Send Email With Template, or the header X-Sendpost-Validate: false.
  • The webhook event type list now shows SMTP Dropped as 26, its real value (it was listed as 10).
Existing integrations are unaffected: webhooks without validated receive the same events as before, and validated only appears in webhook responses when it is on.
Email

tpspFrom sender for third-party providers

Send Email and Send Email With Template accept an optional tpspFrom sender. Emails delivered through a third-party sending provider (SendGrid, Amazon SES, Mailgun, SparkPost, Postmark or Custom SMTP) go out as tpspFrom; emails delivered on SendPost IPs still go out as from.
  • tpspFrom must be on the same registered domain as from, and must be set up as a sender at the provider.
  • Stats and webhook events stay under from.
  • An invalid tpspFrom rejects the request with HTTP 400 and errorCode 11.
Existing integrations are unaffected: without tpspFrom, sending works exactly as before.
v1 contract

Versioned response contract (legacy → v1)

The public REST API now supports two response contracts, selected per request with the X-SendPost-Public-Contract header.
  • legacy (today’s shape) remains the default — existing integrations are unaffected.
  • v1 is available now via X-SendPost-Public-Contract: v1.
  • Responses carry Deprecation, Sunset (31 March 2027, 23:59:59 UTC), and Link headers. After the sunset, v1 becomes the default.
See API Versioning & Migration for the mechanism and step-by-step migration.

What v1 changes, field by field

v1 normalizes field casing, removes internal/sensitive fields, and adds a few new fields. The complete diff per resource:
  • Added: reasonText — public reason label (manual / unsubscribe / hardBounce / spamComplaint / unknown).
  • Removed: groups, ipId, accountIPPoolId (plus dashboard-only ipPublicIP, ipPoolName).
  • Note: smtpError is unchanged (already camelCase in legacy — it is not renamed).
  • Added: autoWarmupEnabled (boolean; computed from warmup state).
  • Renamed: publicIP → publicIp, reverseDNSHostname → reverseDnsHostname.
  • Removed: systemDomain, accountIPPools, autoWarmupPlan (replaced by autoWarmupEnabled), deliverabilityPolicies, and all per-provider settings blobs (gmailSettings … checkpointSettings).
  • Sub-account pools: add shouldOverflow, overflowPoolName. No removals.
  • Account pools: removed thirdPartySendingProviders, toAccountIPPools, warmupInterval, warmupIntervalSeconds, warmupQueueMode, resolveTemplateLocally.
  • Nested ips[] follow the IP changes above.
  • Added: dnsProvider (detected DNS host).
  • Removed: gpt, gptVerified, dkimDelegationInfo, dmarcDelegationInfo, returnPathDelegationInfo, trackDelegationInfo.
  • Unchanged under v1: the domain/v2 endpoints (/subaccount/domain/v2, /account/domain/v2) return the same shape for legacy and v1.
  • Renamed: messageID → messageId, subAccountID → subAccountId, publicIP → publicIp.
  • Removed: customFields, resolveTemplateLocally (plus internal-plumbing fields already hidden from legacy).
  • Renamed: eventID → eventId, messageID → messageId, messageSubject → subject.
  • Removed (top-level): smtpCode, smtpDescription (still available nested under eventMetadata), plus mock-send flags.
  • eventMetadata: renamed clickedURL → clickedUrl, trackedIP → trackedIp, cityID → cityId; removed sendpostLinkId and all provider-specific fields (sendgrid*, ses*, sparkpost*, mailgun*, customSmtp*, postmark*).
  • Removed: smtpAuths, blocked_at, hb_exempt, generate_weekly_report, skip_global_suppression_check, handlers (plus block_reason, created_by, updated_by already hidden from legacy).
  • apiKey, accountId are unchanged.
  • Renamed: url → logoUrl.
  • Removed: isForbidden, notesColor, twoFactorEnabled, twoFactorVerifiedAt (plus firebaseUID, isGodMode already hidden from legacy).
  • Removed: secret (HMAC signing key), eventSubOptions. secret remains available on the legacy contract for same-account callers.
  • List variant (successRate): v1 always returns a number; legacy returns null when unmeasured.
  • Renamed: logoURL → logoUrl.
  • Removed: all billing internals (stripe*, creditBalance, promoCode, isLegacyBilling, trialPeriod, …), moderation/ops fields (demoLocked*, *SlackEndpoint, slackToken, rbacEnabled, hubspotCompanyId, …) and audit fields. Public callers already receive the curated v1 shape today.
  • EmailResponse: no field changes (to, submittedAt, messageId, errorCode, message).
  • Request body: the TPSP routing internals tpspIppool, tpspTemplate, tpspIp are not part of the v1 shape.
Pervasive v1 changes across the stat family:
  • Removed everywhere: the bot/human breakdown botOpened, botClicked, humanOpened, humanClicked; the ops-only spamRate, totalQD, warmupVersion, warmupSkipped; and the raw day / month / year date columns.
  • Renamed: publicIP → publicIp, ipid → ipId; provider stats email_type → emailType (and email_type/name → provider on the provider breakdown); sub-account stats sid → subAccountId, name → subAccountName; account IP-pool stats accountIPPoolId → ipPoolId, name → ipPoolName.
  • seedContactStats nested keys renamed: rStat → rStats, agStat → groupStats, agDomainStat → domainStats, aIPStat → ipStats, aPStat → providerStats.
  • blacklistResource normalized from Snake_Case/PascalCase to camelCase throughout (Add_Date → addDate, Last_Check → lastCheck, Contact_List_ID → contactListId, etc.).
  • ?filter=bot returns a normalized { enabled, raw, filtered, botOpened, botClicked } view under v1.
  • Unchanged: dateStat, validationStat, accountCycleUsage, postmasterDomainStat.
For the authoritative response of any endpoint under v1, the schema on that endpoint’s API Reference page is the source of truth.