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.approvedfor 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.activatedonce, not twice. - Fixed: A market that pre-registers its sender no longer reports a
channel.deactivatedit 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_codeis a stable code to branch on.reasonis 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 balanceBUSINESS_003, an invalid numberVALIDATION_002. - Outcomes only a message can have are
DELIVERY_001toDELIVERY_016. The codes are Sent's classification and never a carrier's own code. - New
BUSINESS_017toBUSINESS_020, includingBUSINESS_020for 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}/eventson 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_profilekeeps the clones' events. Send"sender_profile": {"event_types": []}to delete the clones. event_typescan be empty whensender_profile.event_typesisn'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 setsender_profileand 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_urlsonPOST /v3/messagesand a template'smms.mediaeach take one item, and a second is rejected with400. 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
HEADrequest. 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 tommsfails. A size your server does not declare never blocks the send. See Media Size Limits. audioremoved from the MMS media types. A template media item declaringaudiois 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-profileswith an explicitbillingobject. 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:00or2026-10-01T07:00:00Z. A timestamp without one is rejected with400rather 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
SCHEDULEDandGET /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
SCHEDULEDas 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.activatedfires 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.deactivatedfires 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.submittedonce that market's documents are in, andchannel.changes_requestedonly while documents are outstanding. It previously reportedchanges_requestedin both cases, which asked you to correct something thatGET /v3/compliance/requirementsreported 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.activatedtoo.
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-Attemptcarries the index of the attempt in hand, starting at1, 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:
channelson a template response lists every channel the definition's body can render on, following each channel's send-time fallback, which is why ansmsandwhatsapppair reportsrcsas 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/whatsappdoes not acceptaccess_token,solution_id, orowner_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}, andPOST /v3/profiles/{profileId}/complete. Use/v3/sender-profiles. - Deprecated:
POST/GET /v3/profiles/{profileId}/campaignsandPUT/DELETE /v3/profiles/{profileId}/campaigns/{campaignId}. A campaign is a registration rather than a collection, so it now travels incompliance.campaignon the market call, one per market. - Deprecated:
DELETE /v3/contacts/{id}. UsePATCH /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:
| Field | Where | Reads back |
|---|---|---|
inherit_contacts, inherit_templates | POST/PATCH /v3/profiles, profile and organization responses | false |
allow_contact_sharing, allow_template_sharing | The same | false |
sending_phone_number_profile_id, sending_whatsapp_number_profile_id | PATCH /v3/profiles/{profileId}, profile responses | null |
- Changed:
is_inheritedon the contact response is alwaysfalse. 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_channelonPATCH /v3/contacts/{id}now returns an invalid-enum error naming the accepted values rather than a generic validation failure. - Changed:
primary_use_caseon the brand is alwaysnull. The campaign's use cases replace it. - Changed:
is_welcome_playgroundonGET /v3/templatesis accepted and ignored. The filter it drove was removed. - Changed:
GET /v3/meno longer returns a stubbed organization in sandbox mode, and the four sharing settings are gone from itsprofilesentries. - Unchanged:
POST /v3/messagesstill has no synchronous402. Its declared responses are now202,400,401,403,404, and500, which matches the asynchronous balance gate the docs have described since July: the send is accepted with202and the affected messages are reported asBLOCKED.
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_idon the brand embedded inPOST /v3/profiles,GET /v3/profiles, andGET/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 theVALIDATION_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, notFAILED. The gate runs before routing, so nothing is handed to a carrier: these messages are excluded from your deliverability rate and firemessage.blockedrather thanmessage.failed. - Changed: A free-form send with
channelomitted 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 tochannel: "whatsapp"still fails withWHATSAPP_TEMPLATE_REQUIRED, which remains aFAILEDstatus. - Changed: The
message.blockedwebhook 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.
STARTandHELPdo. - Unchanged:
VALIDATION_004itself. It remains the code for a genuinely missing required field, returned as400on 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_volumefromBrandComplianceInfo(brand request body) andBrandComplianceResponse(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:
volumefield 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 below2000register the campaign at the low-volume tier (capped at 2,000 messages/day, lower monthly fee);2000and 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
volumecauses 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.
| Before | After |
|---|---|
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 brand | Set 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 with403and codeBUSINESS_014and 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 toROUTEDonce 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 behavior | New behavior |
|---|---|
FAILED covered all non-delivered outcomes | FAILED = downstream delivery attempt that failed (carrier/network) |
| No equivalent | FILTERED = suppressed by a DENY rule, which is policy-driven rather than a failure |
| No equivalent | BLOCKED = gated by an account condition before send |
| No equivalent | SCHEDULED = 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.receivedwebhook 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.
Compliance & Consent
- Added: WhatsApp contacts who send a keyword command (
STOP,START,HELP) now automatically receive a reply, and aSTOPreply updates the contact'sopt_outstatus as returned byGET /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
channelomitted (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/conversationsandGET /v3/conversations/{id}endpoints to list a customer's messages grouped by conversation, with pagination.
WhatsApp Messaging
- Added:
POST /v3/messagesnow accepts a plain-texttextfield as an alternative totemplate, for free-form WhatsApp messages within the 24-hour customer-service window. Sending free-form text outside that window now returns a dedicatedWHATSAPP_TEMPLATE_REQUIREDerror. - 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_012error. - 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:
GETcampaign responses now include apricingobject (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
DELIVEREDas 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/STOPcompliance 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
Pre-Send Consent Gate
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_BLOCKEDsend-time error code. Every outbound message now runs through a pre-routing consent check that refuses sends to recipients withopt_out = trueor whose phone is on the customer's phone-channel suppression list. Blocked messages are finalized asFAILEDbefore any provider call is made, so no carrier charge applies. Inspectdescriptionon the message detail or inGET /v3/messages/{id}/activitiesto see the reason. - Updated:
BUSINESS_004is 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 withERR_CONSENT_BLOCKED. See API Errors and the error catalog.
Inbound Message Webhooks
- Fixed:
message.receivedwebhook events now fire correctly for inbound messages across all channels. Previously, subscribing tomessage.receivedproduced 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 amessage.receivedevent to your webhook endpoint. - Updated:
message.receivedpayloadchannelfield now reflects the actual inbound channel (smsorwhatsapp). WhatsApp replies fire the samemessage.receivedevent 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}/completenow 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
objecttype, which made it difficult to distinguish synchronous completion from asynchronous processing in generated SDK clients.
- 200: Profile was already in a completed state. Response body:
API Schema
- Updated:
event_dataandmetadatafields 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(wassub_type). Inbound message payloads:from/torenamed toinbound_number/outbound_number, theproviderfield was removed, andupdated_atwas added. Outbound message payloads gainedupdated_atandtemplate_namefields. - Updated:
message.sentnow reflects successful submission to the upstream provider; provider-side acknowledgement signals that previously could report asmessage.sentnow resolve tomessage.delivered, so you see one clear status after submission (a latermessage.failedcan still follow).message.readwebhooks 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"andstatus: "RECEIVED". - Added: Inbound messages generate a
message.receivedwebhook 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
fromfield 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_filtersfield. When creating or updating a webhook, you can now specify which message sub-types to receive per event type (for example, onlydeliveredandfailed) 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_suspendederror response on all API calls. This applies to both the messaging API and webhook delivery.
Error Documentation Links
- Updated: All API error responses now include a
doc_urlfield 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
metadataobject 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 usersPOST /v3/users: invite a user by email with a specified rolePATCH /v3/users/{id}: update a user's roleDELETE /v3/users/{id}: remove a user from the organization
- Added: Role-based access:
OWNER,ADMIN, andMEMBERroles with appropriate permission scopes. Owners have implicitADMINaccess 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_codefield 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-typesendpoint to list available event types - Added:
POST /v3/webhooks/{id}/testendpoint to test webhook delivery - Added:
POST /v3/webhooks/{id}/rotate-secretendpoint for security rotation - Added:
PATCH /v3/webhooks/{id}/toggle-statusendpoint to toggle webhook status
User Management
- Added:
GET /v3/usersandGET /v3/users/{id}for user listing and details - Added:
POST /v3/usersto 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 brandGET /v3/brands- List brandsPUT /v3/brands/{id}- Update brandDELETE /v3/brands/{id}- Delete brand
- Added: Campaign management endpoints (paths since moved under Sender Profiles)
POST /v3/brands/{id}/campaigns- Create campaign (nowPOST /v3/profiles/{profileId}/campaigns)GET /v3/brands/{id}/campaigns- List campaigns (nowGET /v3/profiles/{profileId}/campaigns)PUT /v3/brands/{id}/campaigns/{id}- Update campaign (nowPUT /v3/profiles/{profileId}/campaigns/{campaignId})DELETE /v3/brands/{id}/campaigns/{id}- Delete campaign (nowDELETE /v3/profiles/{profileId}/campaigns/{campaignId})
Profile Management
- Added: Full profile CRUD operations
POST /v3/profiles- Create profileGET /v3/profiles- List profilesGET /v3/profiles/{id}- Get profile detailsPATCH /v3/profiles/{id}- Update profileDELETE /v3/profiles/{id}- Delete profile
- Added:
POST /v3/profiles/{id}/completefor profile setup completion
September 2024
API v3 Beta Release
- Added: Core messaging endpoints
POST /v3/messages- Send messagesGET /v3/messages/{id}- Get message statusGET /v3/messages/{id}/activities- Get message activities
- Added: Contact management
GET /v3/contacts- List contactsPOST /v3/contacts- Create contactGET /v3/contacts/{id}- Get contactPATCH /v3/contacts/{id}- Update contactDELETE /v3/contacts/{id}- Delete contact
- Added: Template management
GET /v3/templates- List templatesPOST /v3/templates- Create templateGET /v3/templates/{id}- Get templatePUT /v3/templates/{id}- Update templateDELETE /v3/templates/{id}- Delete template
- Added: Webhook management
GET /v3/webhooks- List webhooksPOST /v3/webhooks- Create webhookGET /v3/webhooks/{id}- Get webhookPUT /v3/webhooks/{id}- Update webhookDELETE /v3/webhooks/{id}- Delete webhookGET /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-Keyheader - 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 contactPOST /v2/messages/phone- Send message to phone numberGET /v2/contacts- List contactsGET /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.