Changelog

A chronological record of platform updates, new features, and important changes to Sent's APIs and services. For a summary of what changed between API versions, see API v2 to v3 changes.


2026

September 2026

Channels: channel.rejected, Corrected Event Timing, and Stricter SMS Market Validation

One new channel sub-type, three timing fixes, and two validation changes to POST /v3/channels/sms, one of them breaking.

Breaking: a market that pre-registers its sender must be sent with its documents. POST /v3/channels/sms for a market whose requirements include a document now answers 422 when that document isn't attached or an uploaded file is rejected, naming each missing key under compliance. It previously answered 201 or 202 and left a market waiting on a file the call had promised to collect. The market isn't added, so nothing needs cleaning up.

  • Action required if you add such markets with a JSON body: send the request as multipart/form-data, with each document as a part named after its requirement key. See Add the market.

Changed: sender_value is required for ALPHANUMERIC. POST /v3/channels/sms and POST /v3/sender-profiles answer 400 when number_type is ALPHANUMERIC and sender_value is missing or blank. An alphanumeric sender is one you choose, so there is nothing to provision without it.

Added: channel.rejected. A market's sender that is turned down before it ever went live now has its own sub-type: The Campaign Registry rejecting its campaign, or a sender still being set up being withdrawn. payload.reason carries the explanation when one was given. Both used to be indistinguishable from channel.deactivated, which means a sender that worked stopped. Subscribe to channel or add the rejected filter to receive it. See Channel sub-types.

Channel events: corrected timing.

  • Changed: channel.approved for a 10DLC market fires when the carriers elect your campaign. It previously fired when the campaign was shared with an upstream connectivity partner, days before a verdict existed.
  • Fixed: Submitting a brand or campaign to The Campaign Registry now reports channel.submitted. It previously reported nothing.
  • Fixed: Requesting a sender for a market that registers with nobody reports channel.activated once, not twice.
  • Fixed: A market that pre-registers its sender no longer reports a channel.deactivated it never earned on the way to going live.

Messages and Channels: Reason Codes for Every Non-Success Status

A message that doesn't go out, and a channel that can't send, now say why.

Added: reason_code and reason on messages. message.failed, message.filtered and message.blocked webhooks, GET /v3/messages/{id} (top level and on each event) and GET /v3/messages/{id}/activities carry both. They're omitted on every successful status. See Message Outcome Codes.

  • reason_code is a stable code to branch on. reason is a sentence to show a person, and its wording may improve.
  • A cause the API already rejects synchronously keeps its code: an opted-out recipient is BUSINESS_004, a low balance BUSINESS_003, an invalid number VALIDATION_002.
  • Outcomes only a message can have are DELIVERY_001 to DELIVERY_016. The codes are Sent's classification and never a carrier's own code.
  • New BUSINESS_017 to BUSINESS_020, including BUSINESS_020 for the onboarding-stage message limit.
  • v2 is unchanged.

Added: reason_code on channels. Every SMS market, WhatsApp account and RCS agent that isn't ACTIVE carries reason_code (CHANNEL_001 to CHANNEL_011) and reason, on GET /v3/channels, the single-market reads and Sender Profiles, and on channel.* webhooks for SMS markets. See Channel Status Reasons.

Changed: note is replaced by reason. SMS market responses no longer return note. The same sentence is now reason. Action required if you read note: read reason, and branch on reason_code.

Changed: WhatsApp reports ACTION_NEEDED when it's waiting on you. A WhatsApp channel with no business account connected (CHANNEL_006) or no phone number (CHANNEL_007) used to report PROVISIONING.

SMS: US Territories Reached From a US Number

A US TEN_DLC or LOCAL number now also sends to Puerto Rico, the US Virgin Islands, Guam, American Samoa, the Northern Mariana Islands and the US Minor Outlying Islands. Existing numbers were given the routes, so no action is needed. See Channel Routing.

  • Where a territory accepts alphanumeric senders, Sent sets one up on your first send there and tries it before your number.
  • A failed delivery now reroutes when the carrier refused the sender itself or no route reached the destination network. Each reroute is charged as a send.

MMS: Inbound Attachments on message.received

An inbound MMS now carries a media array: each attachment's url, and its mime_type, size_bytes and hash_sha256 when the carrier declared them. The url is the carrier's own link, which needs no authentication and expires on the carrier's schedule, so download the file on receipt. media is omitted when there are no attachments. See Inbound MMS.

Webhooks: Delivery Fixes

  • Rotated secrets apply to queued events. Each delivery is signed with the webhook's current secret, URL and timeout when it's sent. An event queued before a rotation used to arrive signed with the old secret and fail.
  • Repeated test sends are delivered. Every test uses fresh sample ids. A second test of the same event used to be reported as failed without being sent.
  • Test sends don't count toward auto-disable. A failed test no longer moves the webhook toward being switched off.
  • Your organization's event history includes its clones'. GET /v3/webhooks/{id}/events on your organization's webhook also lists the events its Sender Profile clones delivered.

Webhooks: Sender Profile Clones

Your organization's webhook can be cloned onto every Sender Profile, so every profile's events reach your endpoint.

Added: sender_profile on webhooks. POST /v3/webhooks and PUT /v3/webhooks/{id} accept sender_profile, with its own event_types and event_filters. When it selects any event, Sent clones the webhook onto every Sender Profile under your organization, including profiles you create later. Each clone is a webhook the profile owns, with your webhook's URL and signing secret, and receives the profile's events that sender_profile selects. See Sender Profile Events.

  • Your changes to the webhook update every clone, including one a profile edited. A clone a profile deleted stays deleted.
  • An event a clone delivers names the profile in payload.account_id.
  • Webhook responses don't include sender_profile. It's a request field on create and update only.
  • On an update, omitting sender_profile keeps the clones' events. Send "sender_profile": {"event_types": []} to delete the clones.
  • event_types can be empty when sender_profile.event_types isn't, so a webhook can feed its clones alone.
  • A request made as a Sender Profile, with its own key or with x-profile-id, can't set sender_profile and is answered with a 400.

Changed: your organization's webhook receives only your organization's channel events. A webhook subscribed to channel used to receive its Sender Profiles' channel events as well. A profile's channel events now reach only that profile's webhooks, including clones of yours.

  • Action required if you expect your profiles' channel events at your organization's endpoint: add "sender_profile": {"event_types": ["channel"]} to the webhook.

MMS: One Attachment, a 600 KB Limit, and No Audio

Four changes to MMS on the v3 API. Each moves a failure that used to surface at the carrier, after the message was accepted and charged, to authoring time or to the send itself.

  • One attachment per message, down from 10. media_urls on POST /v3/messages and a template's mms.media each take one item, and a second is rejected with 400. A template saved with more than one attachment before this still sends, carrying only its first.
  • 600 KB per attachment, checked before sending. After accepting the message, Sent reads the size your server declares for the attachment with one HEAD request. Over the limit, an auto-detect send falls to SMS on the same number, with the attachment as a link when it fits, and a send pinned to mms fails. A size your server does not declare never blocks the send. See Media Size Limits.
  • audio removed from the MMS media types. A template media item declaring audio is rejected. Audio was never in the supported formats, so this moves a carrier-side failure to authoring time.
  • Media URLs must use a hostname. A literal IP address is refused with 400.

Sender Profiles: billing.fallback Removed, Plus Resend, Scheduled Sends, and MMS

One removal and three additions on the v3 API, all live as of September 23, 2026.

Removed: billing.fallback on Sender Profiles. The billing object on POST /v3/sender-profiles and in the GET /v3/sender-profiles response now carries inherit only. Both schemas reject unknown fields, so a create request that still sends billing.fallback is answered with a 400 rather than having the field ignored. If you send it, drop it before your next call. Reading it from a response returns nothing, and the two-field shape it replaced could express a state the API never acted on, which is why it went rather than being deprecated in place.

  • Action required if you call POST /v3/sender-profiles with an explicit billing object. Sending {"inherit": ...} alone is correct and unchanged.
  • The field was published on August 31, 2026, so integrations written before then were never able to send it.

Added: POST /v3/messages/{id}/resend. Resend an existing message without rebuilding its payload. Answers 202 on acceptance, with the same response shape as POST /v3/messages and the same message id. It is rate limited more tightly than an ordinary send, because every accepted resend costs money: expect 429 under load rather than silent queueing. 404 for an unknown id and 409 when the message is in a state that cannot be resent.

Added: scheduled_at on POST /v3/messages. Send a message at a future instant instead of now.

  • Requires an explicit UTC offset, such as 2026-10-01T09:00:00+02:00 or 2026-10-01T07:00:00Z. A timestamp without one is rejected with 400 rather than being read in the server's zone. The offset fixes the instant only, and the value is stored and echoed in UTC.
  • Must be at least one minute ahead and at most 30 days ahead.
  • An accepted message reports SCHEDULED and GET /v3/messages/{id} returns the release time.
  • Quiet hours, balance, and template approval are evaluated at release, not at acceptance. A message accepted today can still fail at its release time, so treat SCHEDULED as queued rather than guaranteed.
  • Omit the field to send immediately, exactly as before.

Added: mms as a channel. mms is accepted in channel and reported on messages and templates, and POST /v3/messages takes media_urls and subject alongside it. MMS is enabled per account rather than by request: until your account is enabled for it, an mms candidate is refused and routing falls back, so adding the channel to a send is safe to prepare ahead of time. MMS costs several times an SMS per message, which is why it is opt-in rather than inferred from a template carrying media.

Webhooks: the channel Family, Per-Attempt Signatures, and Rendered Bodies

Three changes to what a subscribed endpoint receives. A handler written against the previous behavior keeps working, but it now sees traffic and fields it did not see before.

Added: the channel event family can be subscribed to. Five sub-types follow one market from filing to sendable, so watching a sender go live no longer means polling GET /v3/channels: channel.submitted, channel.changes_requested, channel.approved, channel.activated, and channel.deactivated. The subject is always one market, never the account, and payload.status answers a different question from the sub-type: the sub-type says what happened, the status says whether the market can send. See Event types and Lifecycle events.

  • Added: channel.activated fires for every market that gains a sender, including markets that register with no regulator and senders assigned for you. Markets that previously raised no events at all raise them now, so expect volume on this family to go up rather than sideways.
  • Added: channel.deactivated fires when a sender ID is cancelled for you, not only when a number returns to the pool or a campaign lapses.
  • Changed: A sender ID that Sent pre-registers with the destination's regulator reports channel.submitted once that market's documents are in, and channel.changes_requested only while documents are outstanding. It previously reported changes_requested in both cases, which asked you to correct something that GET /v3/compliance/requirements reported nothing about.
  • Changed: A 10DLC activation is announced when the campaign is put to work rather than when the registry's webhook lands, so a market that inherits its organization's campaign reports channel.activated too.

Changed: every delivery attempt is signed at send time. X-Webhook-Timestamp and X-Webhook-Signature are computed per attempt, so a retry carries its own timestamp and clears a 5-minute replay window however long the backoff ran before it. Both headers previously carried the event's creation time on every attempt, so a receiver enforcing that window rejected the later retries of an event that was only failing transiently. See Signature verification.

  • Added: X-Webhook-Attempt carries the index of the attempt in hand, starting at 1, so a receiver can tell a redelivery from a first delivery without keeping state of its own.
  • Changed: Deduplicate on X-Webhook-Event-ID, which holds one value for every attempt at the same event. A dedupe key built from the timestamp records one event once per attempt. See Handling retries.

Added: body on outbound message events. Every message.* status event carries payload.body, the rendered message text as plain text, truncated at 3072 characters. The field is always present, and it is null when Sent is not asserting a body: before routing resolves a channel, on a send stopped before it got one, and on a delivery report for an attempt the message has since been rerouted away from. A non-null value is always the render for that event's own channel, so the two fields describe one attempt. The body is plain text, so a WhatsApp or RCS header, footer and buttons are not included; GET /v3/messages/{id} returns the full composed message.

Added: auto_reply_action on templates. GET /v3/templates, GET /v3/templates/{id} and the templates webhook events report which consent keyword a template answers when it is one of the auto-replies Sent creates for you: OPT_IN, OPT_OUT, HELP, or OTHER for a keyword you defined. The field is omitted for a template you authored, so its presence is the answer to whether the row or the event is about an auto-reply. Sent's three compliance auto-replies go through review like any other template, so their events arrive mixed in with your own, and nothing on the wire told them apart before.

Templates: smaller changes.

  • Fixed: An RCS body truncated after variable substitution is cut on a character boundary, so a body with an emoji straddling the 3072-character limit no longer reaches the handset ending in a broken glyph.
  • Clarified: channels on a template response lists every channel the definition's body can render on, following each channel's send-time fallback, which is why an sms and whatsapp pair reports rcs as well. It says what the content can render on, not what may be sent: sending also needs the template approved for that channel. The body, button and editing rules are in the template definition reference.

August 2026

Sender Profiles, Channels, and Compliance Requirements

A profile is now created in one call, its senders are managed per market, and what a market demands is something you can ask for before you try.

Added: /v3/sender-profiles. POST, GET, and GET/PATCH/DELETE on /{id}. POST /v3/sender-profiles needs a name and a short_name and nothing else: identity, inbox, opt-out list, and billing come from your organization, so the profile is usable immediately with no compliance work. There is no completion step and no callback to receive. The response carries the profile's api_key, shown once and never returned again.

Every capability is one of three things, and there is no flag per state: supply the capability's fields to make it dedicated, send {"inherit": true} to take the organization's, or omit the block for neither. A read returns channels.sms as a list, with a status and outstanding compliance per market.

Added: /v3/channels. GET /v3/channels returns every market you send SMS in, your WhatsApp account, and your RCS agent, each with its own status. POST /v3/channels/sms adds a market (a country plus a sender type) and takes everything that market registers in one compliance object. GET/PATCH /v3/channels/sms/{country}/{type} read and correct one market; the PATCH is partial at every level, so an omitted key is left alone, an explicit null clears it, and {} is refused rather than silently accepted. POST /v3/channels/whatsapp connects an account shared through a Multi-Partner Solution, and POST /v3/channels/rcs requests an agent.

  • Added: Status is per channel, never flattened onto the account or the brand: PROVISIONING, ACTION_NEEDED, PENDING_REVIEW, ACTIVE. A sender live in one country and mid-registration in another reports exactly that.
  • Changed: POST /v3/channels/whatsapp does not accept access_token, solution_id, or owner_business_id. The first two are Meta's answer about provenance, and the token rotates roughly every sixty days, so all three are resolved from your organization.

Added: GET /v3/compliance/requirements. Call it first. It returns what a channel, country, and sender type demand (each requirement keyed as you write it, such as brand.legal_name or campaign.message_flow), plus setup[], the calls that satisfy them with a body you can copy and send as it stands. It is the same declaration that validates the calls themselves, so it cannot drift from what the API accepts. channel defaults to sms; whatsapp and rcs return 501 until their requirements are declared, because an empty requirement set would wrongly say those channels demand nothing.

Deprecated: /v3/profiles and the profile-campaign endpoints. All eleven operations still behave exactly as before, so nothing breaks today.

  • Deprecated: POST/GET /v3/profiles, GET/PATCH/DELETE /v3/profiles/{profileId}, and POST /v3/profiles/{profileId}/complete. Use /v3/sender-profiles.
  • Deprecated: POST/GET /v3/profiles/{profileId}/campaigns and PUT/DELETE /v3/profiles/{profileId}/campaigns/{campaignId}. A campaign is a registration rather than a collection, so it now travels in compliance.campaign on the market call, one per market.
  • Deprecated: DELETE /v3/contacts/{id}. Use PATCH /v3/contacts/{id} with {"opt_out": true}, which stops every send and keeps the record of who the contact was and that they asked.

Changed: contact and template sharing between Sender Profiles is gone. A profile sees only its own contacts and templates; the organization still sees those of every profile beneath it, through read-time widening. A tenant's customer data can no longer become visible to a sibling tenant by configuration.

Changed: sender borrowing is gone. A profile cannot be pointed at another profile's number. Give it a dedicated sender with channels.sms. Where sending_phone_number_profile_id is still populated on a read, such as GET /v3/me, it reports which account holds the number in inventory rather than a sender you can choose.

Six fields are now accepted and ignored rather than refused, because a 400 would break an integration that is otherwise working. They stay on the wire so a generated client keeps compiling, and every request carrying one is logged so they can eventually go for real:

FieldWhereReads back
inherit_contacts, inherit_templatesPOST/PATCH /v3/profiles, profile and organization responsesfalse
allow_contact_sharing, allow_template_sharingThe samefalse
sending_phone_number_profile_id, sending_whatsapp_number_profile_idPATCH /v3/profiles/{profileId}, profile responsesnull
  • Changed: is_inherited on the contact response is always false. It stays on the wire so existing clients keep deserializing; do not branch on it.
  • Changed: Inherited contacts are no longer read-only. The gate that rejected an update or a delete of one is gone, along with the contact rows it protected.
  • Changed: An invalid default_channel on PATCH /v3/contacts/{id} now returns an invalid-enum error naming the accepted values rather than a generic validation failure.
  • Changed: primary_use_case on the brand is always null. The campaign's use cases replace it.
  • Changed: is_welcome_playground on GET /v3/templates is accepted and ignored. The filter it drove was removed.
  • Changed: GET /v3/me no longer returns a stubbed organization in sandbox mode, and the four sharing settings are gone from its profiles entries.
  • Unchanged: POST /v3/messages still has no synchronous 402. Its declared responses are now 202, 400, 401, 403, 404, and 500, which matches the asynchronous balance gate the docs have described since July: the send is accepted with 202 and the affected messages are reported as BLOCKED.

Migration: Nothing is required today. When you move, start from GET /v3/compliance/requirements for the market you need, create the profile with POST /v3/sender-profiles, and add its sender with POST /v3/channels/sms. Check existing code for the six ignored fields above: a write to any of them still returns success and does nothing, and reading the value back is how you can tell. See Create and activate Sender Profiles via the API and Managing channels.

Brands: csp_id Deprecated

csp_id on the brand object is deprecated and will be removed in a later release. It's marked deprecated in the OpenAPI specification.

The field identifies the Campaign Service Provider that registered the brand, which is Sent, so the value is the same for every brand and every account. There's no action it supports and no replacement. Stop reading it.

  • Deprecated: csp_id on the brand embedded in POST /v3/profiles, GET /v3/profiles, and GET/PATCH /v3/profiles/{profileId}.
  • Unchanged for now: the field is still returned and still populated, so nothing breaks today. Removal lands in a later release with its own entry here.

Your own TCR identifiers are unaffected. tcr_brand_id and universal_ein stay.

Two-Way Conversations: Free-Form Gating on SMS and RCS

Free-form sends on SMS, RCS, and auto-detect are now governed by an explicit conversation rule rather than a one-time prior-inbound check.

  • Added: A contact who has ever replied, on any channel and at any point in the past, can be sent free-form text indefinitely. A reply is treated as consent to converse and does not expire; only an opt-out ends it.
  • Added: For a contact who has never replied, free-form rides on the last approved template you sent and lapses 7 days after it. The cap exists to stop a one-sided stream of free-form content opened by a single template. Previously a contact who had never replied could not be sent free-form text at all.
  • Added: CONVERSATION_TEMPLATE_REQUIRED, a dedicated code for that blocked case. It replaces the VALIDATION_004 ('template' is required) this case previously produced, which read as a malformed request when the request was fine. The two message variants distinguish a contact with no template ever sent from one whose template has lapsed. Like the other send-time codes, the code itself is recorded internally rather than returned in API responses or webhook payloads.
  • Added: Free-text, an account-level waiver of the conversation rule. With it enabled, free-form reaches a contact who has never replied on SMS, RCS, and auto-detect, with no template first and no 7-day window. It applies on each channel where the account has an active sender, it is set on the organization and inherited by Sender Profiles, and it changes neither Meta's 24-hour window nor opt-out enforcement. Sent enables it on request.
  • Changed: The blocked send is recorded as BLOCKED, not FAILED. The gate runs before routing, so nothing is handed to a carrier: these messages are excluded from your deliverability rate and fire message.blocked rather than message.failed.
  • Changed: A free-form send with channel omitted no longer fails when Meta's WhatsApp window is shut. WhatsApp is treated as unreachable for that send and the message falls back to RCS or SMS. A send pinned to channel: "whatsapp" still fails with WHATSAPP_TEMPLATE_REQUIRED, which remains a FAILED status.
  • Changed: The message.blocked webhook event description now covers all three of its causes (account precondition, template not approved for sending, and no open conversation) rather than account preconditions alone. Description only: no subscription changes, nothing to re-subscribe.
  • Unchanged: Meta's 24-hour customer-service window. Only an inbound WhatsApp message from the contact opens it; an outbound template never does. A send pinned to channel: "whatsapp" answers to that window alone.
  • Unchanged: Opt-out keywords do not count as engagement, on either leg of the exchange. START and HELP do.
  • Unchanged: VALIDATION_004 itself. It remains the code for a genuinely missing required field, returned as 400 on the request.

Migration: If you branch on VALIDATION_004 to detect a blocked free-form send, that branch no longer fires. If you subscribe only to message.failed to catch these, subscribe to message.blocked as well: the send is accepted with 202 and the message finalizes as BLOCKED. Contacts who have replied to you at least once need no action, since free-form to them is never blocked by this rule.

July 2026

10DLC: Messaging Volume Moved to Campaign (Breaking Change)

Expected messaging volume is now a per-campaign field, not a brand-level attribute. Volume drives the TCR tier classification (low-volume vs. standard) and the monthly campaign fee. These are per-campaign concerns: a brand can have multiple campaigns at different tiers.

  • Removed: expected_messaging_volume from BrandComplianceInfo (brand request body) and BrandComplianceResponse (brand response). The field is no longer accepted or returned on any brand endpoint.
  • Removed: The dedicated update-brand-volume endpoint has been deleted. There is no replacement at the brand level.
  • Added: volume field on campaign create (POST /v3/profiles/{profileId}/campaigns), update (PUT /v3/profiles/{profileId}/campaigns/{campaignId}), and get (GET /v3/profiles/{profileId}/campaigns) endpoints. The value is a numeric string representing expected daily message volume (for example "1500"). Values strictly below 2000 register the campaign at the low-volume tier (capped at 2,000 messages/day, lower monthly fee); 2000 and above register as standard. The field is optional, and omitting it registers the campaign as standard, the higher-fee tier, rather than returning an error.
  • Changed: When updating a campaign's volume causes a tier change, the billing delta for the current window is applied immediately rather than being deferred to the next billing sweep.

Migration: Move expected_messaging_volume from brand requests to the volume field on each campaign request. Set volume explicitly on every low-volume campaign. A request that still sends the brand-level field is accepted and that field is ignored, so a campaign left without volume registers as standard and bills at the higher monthly fee, with nothing surfaced to flag it.

BeforeAfter
BrandComplianceInfo.expected_messaging_volume (brand request body)CampaignData.volume (campaign create/update body)
BrandComplianceResponse.expected_messaging_volume (brand response)TcrCampaignWithUseCasesResponse.volume (campaign response)
Single value shared across all campaigns under a brandSet independently per campaign

Message Status Model: New Values

Three new MessageStatus values have been introduced to distinguish policy-driven suppression from delivery failures:

  • Added: FILTERED: message suppressed by a DENY routing rule before reaching a carrier. Policy-driven and expected. Does not count against your deliverability rate. The specific policy that stopped the send is recorded internally and is not returned in API responses or webhook payloads. Webhook event: message.filtered.
  • Added: BLOCKED: message gated before send evaluation due to an account-level condition such as insufficient balance, an unmet onboarding entitlement, or a template that is not approved for sending. Does not count against your deliverability rate. The specific condition is recorded internally and is not returned in API responses or webhook payloads. Webhook event: message.blocked. Sends from a suspended account are rejected at the API edge with 403 and code BUSINESS_014 and never create a message record, so they do not produce this status.
  • Added: SCHEDULED: message queued for future delivery (curfew window or scheduled send). Not a terminal state; it transitions to ROUTED once the window opens. The release time is not exposed as an API field. Webhook event: message.scheduled.

Migration note: Prior to this release, all non-delivered outcomes were surfaced as FAILED. If your integration filters on FAILED to catch all unsent messages, also handle FILTERED and BLOCKED to maintain full coverage. FILTERED and BLOCKED are excluded from the deliverability rate denominator, so only FAILED counts against the rate.

Old behaviorNew behavior
FAILED covered all non-delivered outcomesFAILED = downstream delivery attempt that failed (carrier/network)
No equivalentFILTERED = suppressed by a DENY rule, which is policy-driven rather than a failure
No equivalentBLOCKED = gated by an account condition before send
No equivalentSCHEDULED = deferred to a future window (transient, not terminal)

Message Scheduling & Quiet Hours

  • Added: Messages destined for a mobile network with quiet-hours restrictions are now automatically deferred (status SCHEDULED) instead of being sent or failed during the restricted window, and released once it ends.
  • Fixed: Quiet-hours windows that span midnight are now evaluated correctly.

Delivery Reliability

  • Added: SMS messages that fail delivery on their original channel are now automatically retried on an alternate channel. This automatic fallback now also covers WhatsApp messages that can't be delivered.
  • Fixed: A delivery receipt from a carrier could occasionally be matched to the wrong send attempt after a message was retried across channels, leaving its status stuck. Delivery status now updates correctly in this case.
  • Fixed: message.received webhook events were not firing for some inbound WhatsApp and SMS auto-reply messages, and for messages sent to shared/ported numbers or redelivered inbound messages. These now fire correctly.
  • Added: WhatsApp contacts who send a keyword command (STOP, START, HELP) now automatically receive a reply, and a STOP reply updates the contact's opt_out status as returned by GET /v3/contacts/{id}.
  • Updated: Stricter validation now applies to AUTHENTICATION-category WhatsApp template bodies for the SMS channel, and for link/URL variables.

API Reliability & Security

  • Fixed: Sending a free-form (non-template) message with channel omitted (auto-detect) could bypass the conversation-window gate that normally restricts free-form SMS/RCS sends to an active conversation. Auto-detect sends are now gated the same as explicit-channel sends.
  • Fixed: Authentication-failure lockouts (429) are now scoped to the specific API key rather than the caller's IP address, so a customer sharing an IP with another tenant is no longer at risk of being locked out by unrelated failed attempts.
  • Fixed: DELETE /v3/templates/{id} could fail for templates with channel-status or WhatsApp update-history records; deletion now cleans these up first.

Dashboard

  • Added: Sign in and sign up with Google, GitHub, or Facebook, in addition to email and password.
  • Added: A new bulk-messaging tab in the Playground lets you upload a CSV of recipients and send to the whole list at once, with upfront validation of phone number formats.
  • Added: Message activity now shows a segment count (for example, "3 parts") for multi-part SMS messages.

June 2026

Conversations

  • Added: New GET /v3/conversations and GET /v3/conversations/{id} endpoints to list a customer's messages grouped by conversation, with pagination.

WhatsApp Messaging

  • Added: POST /v3/messages now accepts a plain-text text field as an alternative to template, for free-form WhatsApp messages within the 24-hour customer-service window. Sending free-form text outside that window now returns a dedicated WHATSAPP_TEMPLATE_REQUIRED error.
  • Added: WhatsApp templates can now be approved and sent per channel independently. A template pending or rejected on one channel no longer blocks sending on a channel where it's approved. Sending on a channel where the template isn't active now returns a dedicated BUSINESS_012 error.
  • Fixed: Templates approved without a connected WhatsApp Business Account (for example, compliance-approved for SMS only) can now actually be sent, instead of being blocked for lacking a WhatsApp template ID.

Billing Accuracy

  • Fixed: Multi-part SMS messages (over the 160-character GSM segment limit) are now billed per segment rather than as a single message.
  • Fixed: WhatsApp messages billed directly by Meta to your WhatsApp Business Account are now only charged the difference against Sent's own price for that message, preventing double-charging.
  • Updated: The recurring per-contact messaging charge now runs on a rolling 28-day cycle from the last charge, rather than resetting on the first of each calendar month.
  • Fixed: Corrected per-network SMS cost resolution to always use the actual matching rate for a destination.

10DLC Compliance

  • Added: GET campaign responses now include a pricing object (the resolved recurring campaign fee) and a flag indicating whether the one-time submission fee has been charged.
  • Updated: 10DLC campaign submission and recurring maintenance fees are now billed on each campaign's own activation anniversary, rather than a shared calendar cycle.
  • Fixed: Campaign submission fees that were missed on a brand resubmission are now correctly backfilled and charged.
  • Fixed: Campaign activation now reliably registers the connected phone number with the carrier, so an approved campaign is actually enabled for sending.

Reliability

  • Fixed: WhatsApp Business Account health is now determined the same way across onboarding, Meta webhooks, and send-time checks, reducing inconsistent "WhatsApp degraded" errors.
  • Fixed: SMS delivery status is no longer marked DELIVERED as soon as a carrier accepts a message for transit. It now stays in transit until an actual delivery or failure signal arrives.
  • Fixed: Contact opt-out (STOP) handling is now unified onto a single consent record, so a contact's opt-out status is consistent regardless of which channel they opted out on. HELP/STOP compliance auto-replies are no longer blocked by a contact's own opt-out status.
  • Fixed: A deny routing rule with no fallback configured is now a hard block, instead of occasionally falling through to another route and sending anyway.

Dashboard

  • Added: Sub-account (Sender Profile) SMS configuration and pricing can now automatically inherit from the parent organization's defaults.
  • Added: Self-service auto-recharge: enable auto-charge to automatically top up your balance from a saved card when it drops below a chosen threshold.
  • Added: A restructured Brand Registration (10DLC) wizard for compliance onboarding.
  • Added: Dedicated Sender Identity requests now support uploading required registration documents directly, with document-type status shown in the flow.
  • Added: Campaign details now offer an AI-assisted quality check and rewrite of your campaign messaging.

May 2026

Superseded: Consent-blocked messages are finalized as FILTERED, not FAILED, and surface on the message.filtered webhook. POST /v3/messages no longer rejects a send over recipient opt-out state: the all-recipients-opted-out case below is also accepted with 202 and blocked asynchronously per message. See the error catalog.

  • Added: New ERR_CONSENT_BLOCKED send-time error code. Every outbound message now runs through a pre-routing consent check that refuses sends to recipients with opt_out = true or whose phone is on the customer's phone-channel suppression list. Blocked messages are finalized as FAILED before any provider call is made, so no carrier charge applies. Inspect description on the message detail or in GET /v3/messages/{id}/activities to see the reason.
  • Updated: BUSINESS_004 is now scoped to the all-recipients-opted-out case. When some recipients are still reachable, the API accepts the batch and only the blocked recipients fail asynchronously with ERR_CONSENT_BLOCKED. See API Errors and the error catalog.

Inbound Message Webhooks

  • Fixed: message.received webhook events now fire correctly for inbound messages across all channels. Previously, subscribing to message.received produced no deliveries despite the event appearing in the subscription list. The webhook notification was only sent for outbound status changes. All inbound messages persisted since this release trigger a message.received event to your webhook endpoint.
  • Updated: message.received payload channel field now reflects the actual inbound channel (sms or whatsapp). WhatsApp replies fire the same message.received event shape as SMS replies.
  • Fixed: Inbound WhatsApp replies now reliably resolve to the correct account when a number has been used across multiple WhatsApp Business Accounts.

Profile Completion API

  • Updated: POST /v3/profiles/{profileId}/complete now returns distinct typed responses:
    • 200: Profile was already in a completed state. Response body: {success: true, data: {status: "completed", message: "..."}}.
    • 202: Completion accepted and running in the background. Response body: {success: true, data: {message: "..."}}. The provided webhook URL is called when processing finishes.
    • Previously both paths returned an opaque object type, which made it difficult to distinguish synchronous completion from asynchronous processing in generated SDK clients.

API Schema

  • Updated: event_data and metadata fields on webhook event and message objects are now typed as open JSON objects in the OpenAPI spec. This resolves ambiguity in generated SDK clients where these fields previously appeared as untyped empty schemas. Runtime behaviour is unchanged.

Webhook Payload Standardization

  • Updated: Standardized the message webhook payload shape. The event-type field is now named event (was sub_type). Inbound message payloads: from/to renamed to inbound_number/outbound_number, the provider field was removed, and updated_at was added. Outbound message payloads gained updated_at and template_name fields.
  • Updated: message.sent now reflects successful submission to the upstream provider; provider-side acknowledgement signals that previously could report as message.sent now resolve to message.delivered, so you see one clear status after submission (a later message.failed can still follow). message.read webhooks now cover WhatsApp and RCS (previously WhatsApp only), and are never emitted for SMS.

WhatsApp Template Reliability

  • Fixed: Several classes of WhatsApp template were silently stuck and never actually submitted to Meta for approval: default compliance auto-reply templates (STOP/START/HELP), templates copied from the template library, and templates in a bulk submission with an empty name or unresolved language. These now submit correctly, and previously stuck templates were backfilled.
  • Added: WhatsApp template URL buttons now support a dynamic variable in the button URL, for per-recipient personalization.

Compliance & Reliability

  • Fixed: Inbound SMS opt-in (START) keywords are now only honored when the receiving number is currently assigned to your account, preventing consent from being recorded incorrectly after a number is reassigned or released.
  • Fixed: A cross-provider configuration bug could let one SMS provider's delivery/webhook settings bleed into another's, affecting delivery status and webhook routing for some numbers. Resolved.
  • Fixed: Messages that failed before a channel was selected now report the actual (or auto-routed) channel instead of the placeholder value unknown.

April 2026

Opt-In / Opt-Out Keyword Management

  • Added: Per-account keyword management for SMS opt-in and opt-out flows. You can now configure custom keywords (beyond the standard STOP/START/HELP) that trigger automatic replies and consent state changes. Contacts who send an opt-out keyword are automatically suppressed from future messages.
  • Added: Inbound keyword matches now trigger configurable auto-replies, billed as standard outbound messages.

Inbound Message Processing

  • Added: Inbound messages from contacts are now persisted and fully tracked across all supported channels. Each inbound message appears in your message history with direction: "INBOUND" and status: "RECEIVED".
  • Added: Inbound messages generate a message.received webhook event. Subscribe to this event to receive real-time notifications when contacts reply to your messages. See webhook event types for payload details.
  • Added: The from field is now captured on all message activities, so you can see the sender's phone number throughout the full message lifecycle.

Webhook Event Filtering

  • Added: Granular webhook event filtering via the event_filters field. When creating or updating a webhook, you can now specify which message sub-types to receive per event type (for example, only delivered and failed) instead of subscribing to all status transitions. This reduces webhook volume for high-throughput integrations.

March 2026

Template Approval Enforcement

  • Added: Messages using WhatsApp templates that have not been approved by Meta are now blocked before dispatch, with a clear error response. Previously, unapproved templates could reach the provider and produce a delayed failure. Approved templates continue to send without any change.

OTP Delivery via WhatsApp

  • Added: OTP codes can now be delivered via WhatsApp. The platform waits for a delivery confirmation before reporting success, and automatically falls back to SMS if WhatsApp delivery is not confirmed within the timeout window. No API changes required. Channel selection follows your normal routing configuration.

Account Suspension

  • Added: Accounts flagged as suspended are blocked from sending messages and receive a clear account_suspended error response on all API calls. This applies to both the messaging API and webhook delivery.
  • Updated: All API error responses now include a doc_url field pointing to the relevant documentation page for that specific error code. This makes it easier to diagnose and resolve integration issues without searching the docs manually.

Message Metadata

  • Added: Messages now carry a metadata object that captures origin context (for example, the API endpoint that created the message). This field is returned in message detail and activity responses as an open JSON object.

February 2026

Per-Customer Pricing

  • Added: Two billing models are now supported: Active Contact (charged per unique contact reached in a billing period) and Per Message (charged per message sent). Your account's billing model is configured at account setup. Charges are deducted automatically after each successful send.

Number Provisioning

  • Added: Phone numbers associated with your account are now visible via the API. Use GET /v3/numbers/lookup/{phoneNumber} to retrieve carrier and capability information for any number.

Playground Message Endpoint

  • Added: A dedicated endpoint for sending test messages during onboarding, without requiring a fully provisioned profile. Useful for verifying end-to-end message delivery before going live.

January 2026

Organizations & Sender Profiles (Multi-Tenancy)

  • Added: Full multi-tenancy support via Organizations and Sender Profiles. An Organization is the top-level account; Sender Profiles are sub-accounts that can inherit or isolate contacts, templates, brands, and campaigns from the parent organization.
  • Added: Profile CRUD: create, list, retrieve, update, and delete Sender Profiles via the API.
  • Added: POST /v3/profiles/{id}/complete: trigger the compliance setup workflow for a profile (TCR registration, WhatsApp connection).
  • Added: Contact and template inheritance: profiles can be configured to share contacts and templates with the parent organization (inheritContacts, inheritTemplates), or to maintain their own isolated sets.
  • Added: Template sharing: templates created at the organization level can be shared with child profiles without duplication.

User Management

  • Added: Full user management for your organization:
    • GET /v3/users / GET /v3/users/{id}: list and retrieve users
    • POST /v3/users: invite a user by email with a specified role
    • PATCH /v3/users/{id}: update a user's role
    • DELETE /v3/users/{id}: remove a user from the organization
  • Added: Role-based access: OWNER, ADMIN, and MEMBER roles with appropriate permission scopes. Owners have implicit ADMIN access across all endpoints.
  • Added: User invitation emails with organization and profile context included in the invitation link.

WhatsApp Template Synchronization

  • Added: WhatsApp templates are now automatically synchronized from your connected WhatsApp Business Account. Template status updates (approved, rejected, paused) are reflected in real time via webhook-driven sync. No manual re-import needed.
  • Added: Template soft-deletion: when a WhatsApp Business Account is disconnected or deleted, all associated templates are soft-deleted rather than permanently removed, preserving message history.
  • Added: AUTHENTICATION template category support, including OTP and COPY_CODE button types for one-time password flows.

Error Codes

  • Added: Error code system for message sending failures. Each failure now includes a machine-readable error_code field in the message activity, making it straightforward to programmatically distinguish temporary failures (retry) from permanent ones (suppress).

2025

No customer-facing API changes shipped in 2025. Development during the year went into the platform capabilities released in early 2026, including multi-tenancy (Organizations and Sender Profiles), user management, WhatsApp template synchronization, and the error code system. See the January 2026 entries for details.


2024

December 2024

API v3 General Availability

  • Added: Sent API v3 is now the recommended version for all integrations
  • Added: Full API documentation for v3 endpoints
  • Added: New getting started guide for v3

November 2024

Webhook Endpoints

  • Added: GET /v3/webhooks/event-types endpoint to list available event types
  • Added: POST /v3/webhooks/{id}/test endpoint to test webhook delivery
  • Added: POST /v3/webhooks/{id}/rotate-secret endpoint for security rotation
  • Added: PATCH /v3/webhooks/{id}/toggle-status endpoint to toggle webhook status

User Management

  • Added: GET /v3/users and GET /v3/users/{id} for user listing and details
  • Added: POST /v3/users to invite users to your organization
  • Added: PATCH /v3/users/{id} to update user roles
  • Added: DELETE /v3/users/{id} to remove users

October 2024

10DLC Compliance (Brands & Campaigns)

Superseded: The standalone /v3/brands endpoints below no longer exist. Brand registration now flows through Sender Profiles, and campaigns are managed under /v3/profiles/{profileId}/campaigns.

  • Added: Brand registration endpoints for 10DLC compliance (superseded; brand registration now flows through Sender Profiles)
    • POST /v3/brands - Create brand
    • GET /v3/brands - List brands
    • PUT /v3/brands/{id} - Update brand
    • DELETE /v3/brands/{id} - Delete brand
  • Added: Campaign management endpoints (paths since moved under Sender Profiles)
    • POST /v3/brands/{id}/campaigns - Create campaign (now POST /v3/profiles/{profileId}/campaigns)
    • GET /v3/brands/{id}/campaigns - List campaigns (now GET /v3/profiles/{profileId}/campaigns)
    • PUT /v3/brands/{id}/campaigns/{id} - Update campaign (now PUT /v3/profiles/{profileId}/campaigns/{campaignId})
    • DELETE /v3/brands/{id}/campaigns/{id} - Delete campaign (now DELETE /v3/profiles/{profileId}/campaigns/{campaignId})

Profile Management

  • Added: Full profile CRUD operations
    • POST /v3/profiles - Create profile
    • GET /v3/profiles - List profiles
    • GET /v3/profiles/{id} - Get profile details
    • PATCH /v3/profiles/{id} - Update profile
    • DELETE /v3/profiles/{id} - Delete profile
  • Added: POST /v3/profiles/{id}/complete for profile setup completion

September 2024

API v3 Beta Release

  • Added: Core messaging endpoints
    • POST /v3/messages - Send messages
    • GET /v3/messages/{id} - Get message status
    • GET /v3/messages/{id}/activities - Get message activities
  • Added: Contact management
    • GET /v3/contacts - List contacts
    • POST /v3/contacts - Create contact
    • GET /v3/contacts/{id} - Get contact
    • PATCH /v3/contacts/{id} - Update contact
    • DELETE /v3/contacts/{id} - Delete contact
  • Added: Template management
    • GET /v3/templates - List templates
    • POST /v3/templates - Create template
    • GET /v3/templates/{id} - Get template
    • PUT /v3/templates/{id} - Update template
    • DELETE /v3/templates/{id} - Delete template
  • Added: Webhook management
    • GET /v3/webhooks - List webhooks
    • POST /v3/webhooks - Create webhook
    • GET /v3/webhooks/{id} - Get webhook
    • PUT /v3/webhooks/{id} - Update webhook
    • DELETE /v3/webhooks/{id} - Delete webhook
    • GET /v3/webhooks/{id}/events - List webhook events
  • Added: Number lookup
    • GET /v3/numbers/lookup/{phoneNumber} - Look up phone number information
  • Added: Account information
    • GET /v3/me - Get authenticated account details

New Features

  • Added: Sandbox mode (sandbox: true) for all mutation endpoints
  • Added: Idempotency key support via Idempotency-Key header
  • Added: Consistent JSON response envelope (success, data, error, meta)
  • Added: Standardized error codes with documentation URLs
  • Added: snake_case property naming convention
  • Added: Rate limiting with detailed response headers

2023

Legacy API v2

The v2 API remains fully supported for existing integrations. All v2 endpoints continue to operate at /v2/ paths.

Key v2 endpoints:

  • POST /v2/messages/contact - Send message to contact
  • POST /v2/messages/phone - Send message to phone number
  • GET /v2/contacts - List contacts
  • GET /v2/templates - List templates

See Legacy API Reference for complete v2 documentation.


Deprecation Notices

API v2

  • Status: Legacy (still supported)
  • Recommendation: New integrations should use v3
  • End of Support: No end date announced

Subscribe to our API Status page for real-time updates on API changes and service status.


Feedback

Have suggestions for API improvements? Contact support@sent.dm.


On this page

Changelog2026September 2026Channels: channel.rejected, Corrected Event Timing, and Stricter SMS Market ValidationMessages and Channels: Reason Codes for Every Non-Success StatusSMS: US Territories Reached From a US NumberMMS: Inbound Attachments on message.receivedWebhooks: Delivery FixesWebhooks: Sender Profile ClonesMMS: One Attachment, a 600 KB Limit, and No AudioSender Profiles: billing.fallback Removed, Plus Resend, Scheduled Sends, and MMSWebhooks: the channel Family, Per-Attempt Signatures, and Rendered BodiesAugust 2026Sender Profiles, Channels, and Compliance RequirementsBrands: csp_id DeprecatedTwo-Way Conversations: Free-Form Gating on SMS and RCSJuly 202610DLC: Messaging Volume Moved to Campaign (Breaking Change)Message Status Model: New ValuesMessage Scheduling & Quiet HoursDelivery ReliabilityCompliance & ConsentAPI Reliability & SecurityDashboardJune 2026ConversationsWhatsApp MessagingBilling Accuracy10DLC ComplianceReliabilityDashboardMay 2026Pre-Send Consent GateInbound Message WebhooksProfile Completion APIAPI SchemaWebhook Payload StandardizationWhatsApp Template ReliabilityCompliance & ReliabilityApril 2026Opt-In / Opt-Out Keyword ManagementInbound Message ProcessingWebhook Event FilteringMarch 2026Template Approval EnforcementOTP Delivery via WhatsAppAccount SuspensionError Documentation LinksMessage MetadataFebruary 2026Per-Customer PricingNumber ProvisioningPlayground Message EndpointJanuary 2026Organizations & Sender Profiles (Multi-Tenancy)User ManagementWhatsApp Template SynchronizationError Codes20252024December 2024API v3 General AvailabilityNovember 2024Webhook EndpointsUser ManagementOctober 202410DLC Compliance (Brands & Campaigns)Profile ManagementSeptember 2024API v3 Beta ReleaseNew Features2023Legacy API v2Deprecation NoticesAPI v2Feedback