{"object":"api_changelog","entries":[{"id":"2026-11-02-15","date":"2026-11-02","apiVersion":"1.4.0","kind":"added","area":"api","summary":"POST /v1/voice/call can answer 202 with status unknown, and its 500 voice_enqueue_failed answer is now in the contract.","detail":"A 202 means the call could not be confirmed as queued. It may still be placed, so it is not tried again and its charge stays until it settles. The body has the same fields as a queued call plus message, which says whether this was recorded and, if not, asks you to contact support with the callSid. Look the call up with GET /v1/voice/call/{callSid}. A replay with the same Idempotency-Key returns the same 202 and places nothing. This case used to answer 500 and say you were not charged. A 500 voice_enqueue_failed now means the call was not placed, and message says what happened to the charge. A 500 is not stored against the Idempotency-Key, so a retry with the same key runs the request again: read message before you retry. Nothing about a successful call changed.","affects":["voice-call:POST"],"breaking":false},{"id":"2026-11-02-14","date":"2026-11-02","apiVersion":"1.3.0","kind":"changed","area":"api","summary":"A WhatsApp failure reported after the send now returns its charge.","detail":"Applies to the same refusals as a refusal at the moment we send: a bad or unapproved template, an invalid number, a blocked recipient, on the first attempt. A message already marked delivered or read keeps its charge. The message stays failed.","affects":["campaigns:POST","whatsapp-messages:POST"],"breaking":false},{"id":"2026-11-02-13","date":"2026-11-02","apiVersion":"1.3.0","kind":"changed","area":"api","summary":"A voice call or queued WhatsApp message with no answer from the provider now ends as unknown.","detail":"It used to be retried, which could send it twice. It now ends with the status unknown, its charge stays, and it is not sent again. A voice call can still move on if the provider\u0027s status callback arrives. A provider error that is an answer, such as a 5xx or a 429, is retried as before.","affects":["voice-call:POST","campaigns:POST","whatsapp-messages:POST"],"breaking":false},{"id":"2026-11-02-12","date":"2026-11-02","apiVersion":"1.3.0","kind":"added","area":"api","summary":"Two refusals are now in the contract.","detail":"POST /v1/voice/call can answer 503 charge_unavailable when the charge could not be taken right now. Retry after the seconds in Retry-After. POST /v1/campaigns answers 400 template_not_found when the template is not approved on the account. Both are in the OpenAPI document. Nothing about a successful call changed.","affects":["voice-call:POST","campaigns:POST"],"breaking":false},{"id":"2026-11-02-11","date":"2026-11-02","apiVersion":"1.3.0","kind":"changed","area":"api","summary":"A WhatsApp message refused at the moment we send it now returns its charge.","detail":"Applies to queued template messages when WhatsApp refuses the template or the number on the first attempt. The message ends as failed and is not retried. A timeout or a provider error is not a refusal: the message is retried and the charge stays. A failure WhatsApp reports later, after it accepted the message, is not returned automatically. The response of the send is unchanged.","affects":["campaigns:POST","whatsapp-messages:POST"],"breaking":false},{"id":"2026-11-02-10","date":"2026-11-02","apiVersion":"1.3.0","kind":"changed","area":"api","summary":"POST /v1/campaigns on WhatsApp now returns one campaign id for the whole request.","detail":"The response keeps the same fields. campaignId is now camp_ followed by a full GUID, as on the sms channel, and it names the campaign on your Campaigns page, where it can be cancelled until its messages are dispatched. A cancel also stops recipients the request has not reached yet. The name in the request is shown there. Each recipient used to get a separate one-message campaign that the page could not list or cancel, and the returned id matched none of them. Campaigns sent before this change stay as they are. A campaign with one recipient counts as a single send, so the page hides it unless single sends are shown. If nothing was queued no campaign is written, so sentCount 0 means the id names nothing.","affects":["campaigns:POST"],"breaking":false},{"id":"2026-11-02-09","date":"2026-11-02","apiVersion":"1.2.0","kind":"changed","area":"api","summary":"POST /v1/campaigns on WhatsApp now queues each message.","detail":"sentCount counts messages queued, so read delivery from message status or webhooks. failedCount covers refusals before queuing only. A template that is not approved on the account is refused with 400 template_not_found and nothing is charged. The messages now follow the account\u0027s send rate limit, frequency cap and quiet hours.","affects":["campaigns:POST"],"breaking":false},{"id":"2026-11-02-07","date":"2026-11-02","apiVersion":"1.2.0","kind":"added","area":"webhook","summary":"New webhook event: segment.alert.","detail":"Sent when an audience alert rule fires or recovers. The payload names the segment, the rule, its threshold, the size now and a state of firing or recovered. Subscribe to it on the webhook endpoint like any other event.","affects":["segment.alert"],"breaking":false},{"id":"2026-11-02-08","date":"2026-11-02","apiVersion":"1.2.0","kind":"added","area":"api","summary":"The conversion postback answers with payout, currency and payout_source.","detail":"payout_source says where the amount came from: postback when the request sent it, offer_default when the offer\u0027s fixed amount applied. The three fields are left out when no money was recorded. Existing fields are unchanged.","affects":["postback"],"breaking":false},{"id":"2026-11-02-06","date":"2026-11-02","apiVersion":"1.1.0","kind":"security","area":"api","summary":"The conversion pixel no longer sets a payout, and its events are stored as pixel.\u003cname\u003e.","detail":"The pixel is a public, unsigned request, so anyone holding a click id could write an amount to the revenue ledger, or take the event slot of a real postback and block its payout. payout on the pixel URL is now ignored. Send anything that pays through the signed postback. A click records at most five pixel events, a pixel event sends no conversion.recorded webhook, and pixel and cta_click events no longer use up an offer\u0027s daily cap. This removal has no notice period because it is a security fix.","affects":["pixel"],"breaking":true},{"id":"2026-11-02-05","date":"2026-11-02","apiVersion":"1.1.0","kind":"added","area":"api","summary":"GET /v1/messages and GET /v1/messages/{id} now carry a failure object, and SMS message.failed events carry reason and code.","detail":"failure has reason, reason_text, code and attempts, and is null when there is nothing to report. These two reads and GET /v1/contacts/{phone}/events can answer 403 team_scoping while an account\u0027s team views are on.","affects":["messages:GET","message:GET","contact-events:GET","message.failed"],"breaking":false},{"id":"2026-11-02-04","date":"2026-11-02","apiVersion":"1.1.0","kind":"deprecated","area":"api","summary":"rate_limits.requests_per_second on GET /v1/account is deprecated. Use rate_limits.requests_per_hour.","detail":"The limit Cast enforces is hourly. The old field holds the key\u0027s raw setting, which is 0 when the key has no setting of its own. It stays, with the same value, until the sunset date.","affects":["account:GET#rate_limits.requests_per_second"],"sunset":"2027-05-01","breaking":false},{"id":"2026-11-02-03","date":"2026-11-02","apiVersion":"1.1.0","kind":"deprecated","area":"webhook","summary":"message_id and campaign_id on SMS message events are deprecated. Use messageId and campaignId.","detail":"SMS delivery events now also carry messageId and campaignId, with the same values. The old keys stay until the sunset date.","affects":["message.delivered","message.failed"],"sunset":"2027-05-01","breaking":false},{"id":"2026-11-02-02","date":"2026-11-02","apiVersion":"1.1.0","kind":"added","area":"webhook","summary":"New webhook events for email and conversions, and every conversation event can now be turned on from the console.","detail":"Added email.opened, email.clicked, email.bounced, email.complained, email.unsubscribed, conversion.recorded and conversion.reversed, all off until you tick them on an endpoint. conversation.status_changed, conversation.assigned and note.added can now be ticked at /developers/webhooks. WhatsApp message events gain sentAt. Every delivery\u0027s envelope gains apiVersion. Send test can now send the sample of a chosen event.","affects":["email.opened","email.clicked","email.bounced","email.complained","email.unsubscribed","conversion.recorded","conversion.reversed","conversation.status_changed","conversation.assigned","note.added"],"breaking":false},{"id":"2026-11-02-01","date":"2026-11-02","apiVersion":"1.1.0","kind":"added","area":"api","summary":"Added GET /v1/webhook-events and GET /v1/changelog, rate-limit headers in the contract, and rate_limits.requests_per_hour on GET /v1/account.","detail":"The rate-limit headers were already sent; they are now documented in the OpenAPI spec and readable by browser callers. /v1/webhook-events is the event catalogue as JSON.","affects":["webhook-events:GET","changelog:GET","account:GET"],"breaking":false},{"id":"2026-10-16-01","date":"2026-10-16","apiVersion":"1.0.0","kind":"added","area":"api","summary":"Added GET /v1/audit-log for keys with the audit scope.","affects":["audit-log:GET"],"breaking":false},{"id":"2026-09-07-01","date":"2026-09-07","apiVersion":"1.0.0","kind":"added","area":"api","summary":"Added next_cursor and starting_after to GET /v1/messages and GET /v1/contacts/{phone}/events.","detail":"A keyset cursor keeps pages stable when new rows arrive between calls. offset still works.","affects":["messages:GET","contact-events:GET"],"breaking":false},{"id":"2026-08-22-01","date":"2026-08-22","apiVersion":"1.0.0","kind":"added","area":"api","summary":"POST /v1/push/devices accepts a device_id with no push token.","detail":"An app that uses in-app messages and no push can now register a device.","affects":["push-devices:POST"],"breaking":false},{"id":"2026-08-21-01","date":"2026-08-21","apiVersion":"1.0.0","kind":"added","area":"api","summary":"Added sandbox keys. A sk_test_ key reads your real data and simulates every send.","affects":[],"breaking":false},{"id":"2026-08-19-02","date":"2026-08-19","apiVersion":"1.0.0","kind":"added","area":"api","summary":"Added the OpenAPI contract at GET /v1/openapi.","affects":[],"breaking":false},{"id":"2026-08-19-01","date":"2026-08-19","apiVersion":"1.0.0","kind":"added","area":"api","summary":"Added the Idempotency-Key header on sends.","detail":"A retried request with the same key returns the first answer and does not send twice.","affects":[],"breaking":false},{"id":"2026-08-11-01","date":"2026-08-11","apiVersion":"1.0.0","kind":"added","area":"api","summary":"Added POST /v1/events for sending up to 100 events in one call.","affects":["events:POST"],"breaking":false},{"id":"2026-05-01-01","date":"2026-05-01","apiVersion":"1.0.0","kind":"added","area":"api","summary":"Added the contact properties and contact events endpoints.","detail":"Store per-contact properties for segments, and send custom events that can start a journey.","affects":["contact-properties:GET","contact-properties:POST","contact-events:GET","contact-events:POST"],"breaking":false}],"deprecations":[{"kind":"event_field","target":"message#message_id","label":"message_id on SMS message.delivered and message.failed events","announcedOn":"2026-11-02","sunsetOn":"2027-05-01","replacement":"messageId","changelogId":"2026-11-02-03"},{"kind":"event_field","target":"message#campaign_id","label":"campaign_id on SMS message.delivered and message.failed events","announcedOn":"2026-11-02","sunsetOn":"2027-05-01","replacement":"campaignId","changelogId":"2026-11-02-03"},{"kind":"response_field","target":"account:GET#rate_limits.requests_per_second","label":"rate_limits.requests_per_second on GET /v1/account","announcedOn":"2026-11-02","sunsetOn":"2027-05-01","replacement":"rate_limits.requests_per_hour","changelogId":"2026-11-02-04"}]}