API v2 to v3 Migration

Migrate your integration from the legacy v2 API to the current v3 API.

Key Differences

Featurev2v3
Base URLapi.sent.dm/v2api.sent.dm/v3
Auth Headerx-sender-id + x-api-keyx-api-key only
Request BodyphoneNumber or contactId, templateId, templateVariablesto, template (object with id/name/parameters), channel
Response FormatFlat dataEnvelope with success, data, error, meta
Rate LimitsVaries200 req/min (standard), 10 req/min (sensitive)
Sandbox ModeNot availablesandbox: true
TemplatesSimple textStructured components
ContactsBasicChannel intelligence

Authentication Changes

v2 (Legacy)

GET /v2/messages/{id}
x-sender-id: your-sender-id
x-api-key: your-api-key

v3 (Current)

GET /v3/messages/{id}
x-api-key: your-api-key

Request Format Changes

Sending Messages

v2 (Legacy)

v2 sends to one recipient per request through separate endpoints. POST /v2/messages/phone is shown here; POST /v2/messages/contact takes contactId instead of phoneNumber:

{
  "phoneNumber": "+1234567890",
  "templateId": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
  "templateVariables": {
    "name": "John",
    "order_id": "12345"
  }
}

v3 (Current)

{
  "to": ["+1234567890"],
  "template": {
    "id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
    "name": "order_confirmation",
    "parameters": {
      "name": "John",
      "order_id": "12345"
    }
  },
  "channel": ["sms"],
  "sandbox": false
}

Key Changes:

  • Separate /v2/messages/phone and /v2/messages/contact endpoints → single POST /v3/messages
  • phoneNumber (single string) → to (array of recipients)
  • templateIdtemplate.id (inside the template object)
  • templateVariablestemplate.parameters
  • Added template.name (optional)
  • Added channel: explicit per-request channel selection
  • Added sandbox: sandbox mode is new in v3; v2 has no equivalent

Response Format Changes

v2 (Legacy)

POST /v2/messages/phone returns 202 Accepted with a bare message ID:

{
  "messageId": "8ba7b830-9dad-11d1-80b4-00c04fd430c8"
}

v3 (Current)

{
  "success": true,
  "data": {
    "status": "QUEUED",
    "template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
    "template_name": "order_confirmation",
    "recipients": [
      {
        "message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
        "to": "+1234567890",
        "channel": "sms"
      }
    ]
  },
  "error": null,
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-03-04T11:28:25.2096416+00:00",
    "version": "v3"
  }
}

Key Changes:

  • Response is now wrapped in an envelope with success, data, error, meta
  • Bare messageId → one entry per recipient and channel in data.recipients[], each with its own message_id
  • Added meta.request_id to quote in support tickets

Error Format Changes

v2 (Legacy)

v2 returns RFC 9110 problem details with human-readable text only:

{
  "type": "https://www.rfc-editor.org/rfc/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "PhoneNumber": ["Phone number is required"]
  }
}

v3 (Current)

{
  "success": false,
  "data": null,
  "error": {
    "code": "BUSINESS_003",
    "message": "Account balance is insufficient to send this message",
    "details": null,
    "doc_url": "https://docs.sent.dm/errors/BUSINESS_003"
  },
  "meta": {
    "request_id": "req_abc123",
    "timestamp": "2026-03-04T11:28:25.2096416+00:00",
    "version": "v3"
  }
}

Key Changes:

  • Errors now use the same envelope as success responses (success: false, error, meta) instead of RFC 9110 problem details
  • Added machine-readable error.code values prefixed by category (for example, BUSINESS_003, AUTH_001, VALIDATION_002); match on the code instead of parsing message text
  • Added doc_url with link to error documentation

Webhook Event Changes

v2 (Legacy)

Legacy webhooks delivered a flat body identified by a type field with the plural value messages:

{
  "type": "messages",
  "timestamp": "2025-01-15T08:30:15Z",
  "message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
  "template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
  "recipient_phone_number": "+1234567890",
  "message_status": "DELIVERED",
  "channel": "sms"
}

v3 (Current)

{
  "field": "message",
  "event": "message.delivered",
  "timestamp": "2025-01-15T08:30:15Z",
  "payload": {
    "updated_at": "2025-01-15T08:30:15Z",
    "account_id": "7ba7b820-9dad-11d1-80b4-00c04fd430c8",
    "message_id": "8ba7b830-9dad-11d1-80b4-00c04fd430c8",
    "template_id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8",
    "template_name": "order_confirmation",
    "outbound_number": "+1234567890",
    "message_status": "DELIVERED",
    "channel": "sms"
  }
}

Key Changes:

  • type renamed to field; its value changed from messages (plural) to message (singular)
  • Added event: granular sub-type (such as message.delivered, message.failed)
  • Added payload wrapper: event data is now nested under payload
  • message_id, template_id, message_status, and channel moved into payload
  • recipient_phone_numberpayload.outbound_number
  • Added payload.updated_at, payload.account_id, and payload.template_name
  • Webhooks still subscribed to the legacy messages event type keep receiving all message.* events; see the Events Reference for the full catalog

Endpoint Mapping

v2 Endpointv3 EndpointChanges
POST /v2/messages/contactPOST /v3/messagesSingle send endpoint; new request/response format
POST /v2/messages/phonePOST /v3/messagesSingle send endpoint; new request/response format
GET /v2/messages/{id}GET /v3/messages/{id}Response uses envelope format
GET /v2/contactsGET /v3/contactsResponse uses envelope format
Not available in v2POST /v3/contactsNew in v3: create contacts through the API
POST /v2/templatesPOST /v3/templatesNew template structure

Idempotency Changes

v2 (Legacy)

v2 has no idempotency support: retrying a timed-out request can send the same message twice.

v3 (Current)

curl -X POST "https://api.sent.dm/v3/messages" \
  -H "x-api-key: $SENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_123_456" \
  -d '{
    "to": ["+1234567890"],
    "template": {"id": "9ba7b840-9dad-11d1-80b4-00c04fd430c8"}
  }'

Key Changes:

  • Idempotency is new in v3: send an Idempotency-Key header on write requests to make retries safe
  • Keys are cached for 24 hours

Migration Steps

  1. Update Base URL - Change from /v2 to /v3
  2. Update Authentication - Remove x-sender-id header, keep only x-api-key
  3. Update Request Format - Replace phoneNumber/contactId with the to array, templateId with template.id, and templateVariables with template.parameters
  4. Update Response Handling - Handle the new envelope format with success, data, error, meta
  5. Update Error Handling - Match on the new error.code values (for example, BUSINESS_003) instead of parsing error text
  6. Update Webhook Handlers - Adjust for new nested event structure (payload wrapper added; use payload.message_id and payload.message_status)
  7. Add Idempotency - Send an Idempotency-Key header on write requests (new in v3)
  8. Test in Sandbox Mode - Validate all features with sandbox: true
  9. Deploy Gradually - Use feature flags for rollout

The v2 API is deprecated for new integrations but remains operational, and no sunset date has been announced. New capabilities such as sandbox mode, idempotency keys, and contact creation ship in v3 only, so plan your migration now rather than against a deadline. Watch the API changelog for version and deprecation announcements.


On this page