Skip to main content

Integrations

Twilio API

You can use your current Twilio implementation, via the API or SDK, to send/receive messages through Loop Message. We tried to maintain backward compatibility with the Twilio API, and all you need to do is port your number from Twilio to us or order a new one in Loop Message.

Required changes

  • Port your number from Twilio to us. Or you can order a new one with Loop Message.
  • Need to change the host URL to https://tw-api.loopmessge.com/
  • Use Loop Message API key

And once phone numbers are active in LoopMessage, everything should work.

Below, we’ll describe all parameters and provide examples of using this backward compatibility if you need to modify your implementation.

Using with 3rd party integrations or frameworks

It’s common when you can use Twilio integration in some no-coding tools or CRM.

You need to ensure your integration allows you to override the Twilio host URL or proxy requests through another URL. Otherwise, we’re not able to receive your Twilio requests.

When you integration asking for Twilio SID and Account Auth Token, you can always use the Loop Message API Key for these two parameters.

Send iMessage/SMS/RCS/WhatsApp

POST https://tw-api.loopmessage.com/2010-04-01/Accounts/{loop_message_api_key}/Messages.json

Please note that you need to send a Loop Message API key as the path parameter in your requests. No need to pass the API key in the Authorization header.

Request Body

NameTypeDescription
To*StringPhone number, Email, or Contact ID.
Body*StringYour message text
FromStringOptional. ID of your sender name. Send message from a specific sender name.
MediaUrlArrayOptional. An array of strings. The string must be a full URL of your image. URL should start with https://. HTTP links (without SSL) are not supported. This must be a publicly accessible file URL: we will not be able to reach any URLs that are hidden or that require authentication. Max length of each URL: 256 characters, max elements in the array: 5.
StatusCallbackStringOptional. The URL where you want to receive message status updates. Callback will be sent in Twilio format. Max length: 256 characters.
MessagingServiceSidStringOptional. Sender pool ID create in the dashboard.
EffectStringOptional. Add effect to your message. Possible values: slam, loud, gentle, invisibleInk, echo, spotlight, balloons, confetti, love, lasers, fireworks, shootingStar, celebration You can check the Apple guide about expressive messages.
SubjectStringOptional. Your message subject. A recipient will see this subject as a bold title before the text.
ReplyToIdStringOptional. The message_id that you got from the webhook You can check the Apple guide about the reply to feature.

Recipient phone numbers should be only in international formats with a country code. Otherwise will be impossible to verify a phone number.

Plus prefix + is optional. Spaces, dashes ‘-’, brackets ‘(123)’ - also optional.

Valid phone number format examples:

  • 13231234567
  • +13231111111
  • +1 (323) 1111111
  • +1 323 123 4567
  • 1 (323)-123-4567

In case you need to send WhatsApp, you need to pass the contact in the following format: whatsapp:+13231112233

Important
When you receive a successful response with code 200 from sending a request, it means that the server accepted your request and added it to the queue. But this does not mean that the message was delivered to the recipient or will be sent.

To handle message status for this request, need to observe callbacks or use the API method to check the status by message ID, which you received in the JSON response.

Response example

{
  "account_sid": "{organization_id}",
  "api_version": "2010-04-01",
  "body": "your text",
  "date_created": "Fri, 24 May 2019 17:18:27 +0000",
  "date_sent": "Fri, 24 May 2019 17:18:28 +0000",
  "date_updated": "Fri, 24 May 2019 17:18:28 +0000",
  "direction": "outbound-api",
  "error_code": 30007,  # Optional
  "error_message": "Carrier violation",  # Optional
  "from": "+12019235161",
  "messaging_service_sid": "{pool_id}",
  "num_media": "0",
  "num_segments": "1",
  "price": null,
  "price_unit": "USD",
  "sid": "{message_id}",
  "status": "sent",
  "to": "+13231112233",
  "uri": "/2010-04-01/Accounts/{api_key}/Messages/{message_id}.json"
}

Inbound message

Request example

{
    "From": "+17372222204",
    "To": "+17379990001",
    "Body": "Hello",
    "MessageSid": "string",
    "SmsSid": "string",
    "SmsStatus": "received",
    "ApiVersion": "2010-04-01",
    "AccountSid": "your_api_key",
    "MessagingServiceSid": "string", # Optional
    "NumMedia": "0",
}

Check message status

You can check the message statuses by following

POST https://tw-api.loopmessage.com/2010-04-01/Accounts/{loop_message_api_key}/Messages/{message_id}.json

Please note that you need to send a Loop Message API key as the path parameter in your requests. No need to pass the API key in the Authorization header.

Response example

{
  "account_sid": "{organization_id}",
  "api_version": "2010-04-01",
  "body": "your text",
  "date_created": "Fri, 24 May 2019 17:18:27 +0000",
  "date_sent": "Fri, 24 May 2019 17:18:28 +0000",
  "date_updated": "Fri, 24 May 2019 17:18:28 +0000",
  "direction": "outbound-api",
  "error_code": 30007,  # Optional
  "error_message": "Carrier violation",  # Optional
  "from": "+12019235161",
  "messaging_service_sid": "{pool_id}",  # Optional
  "num_media": "0",
  "num_segments": "1",
  "price": null,
  "price_unit": "USD",
  "sid": "message_id",
  "status": "sent",
  "to": "+18182008801",
  "uri": "/2010-04-01/Accounts/{api_key}/Messages/{message_id}.json"
}

Twilio Python SDK Example

import requests
from twilio.http.http_client import TwilioHttpClient
from twilio.rest import Client
from twilio.http.response import Response as TwilioResponse

# Prepare proxy client
class ProxyTwilioHttpClient(TwilioHttpClient):
    def __init__(self, proxy_base_url: str):
        super().__init__()
        self.proxy_base_url = proxy_base_url.rstrip("/")

    def request(self, method, url, params=None, data=None, headers=None, auth=None, timeout=None, allow_redirects=False):

        rewritten_url = url.replace("https://api.twilio.com", self.proxy_base_url)

        resp = requests.request(
            method=method,
            url=rewritten_url,
            params=params,
            data=data,
            headers=headers,
            auth=auth,
            timeout=timeout,
            allow_redirects=allow_redirects
        )

        return TwilioResponse(resp.status_code, resp.text, resp.headers)

# Send message
def send_message():
    api_key = f'{your_loop_message_api_key}'  # Try to save it in your .env file
    http_client = ProxyTwilioHttpClient('https://tw-api.loopmessage.com/')
    client = Client(username=api_key, password=api_key, account_sid=api_key, http_client=http_client)

    message = client.messages.create(
        from_="+17372221122", body="Hi there", to="+13231112233",
    )
    # optional_fields = {messaging_service_sid="pool_id", media_url: [https://example.com/file.png]}

    print(message.body)
    
# Message status
def message_status():
    api_key = f'{your_loop_message_api_key}'  # Try to save it in your .env file
    http_client = ProxyTwilioHttpClient('https://tw-api.loopmessage.com/')
    client = Client(username=api_key, password=api_key, account_sid=api_key, http_client=http_client)

    message = client.messages("{message_id}").fetch()
    print(message.body)

Twilio TypeScript SDK Example

import axios, { type Method } from "axios";
import qs from "qs";
import twilio, { RestException } from "twilio";

type TwilioRequestOptions = {
  method?: string;
  uri?: string;
  username?: string;
  password?: string;
  headers?: Record<string, string>;
  params?: Record<string, unknown> | null;
  data?: Record<string, unknown> | null;
};

class LoopProxyHttpClient {
  constructor(private readonly proxyBaseUrl: string) {}

  async request(opts: TwilioRequestOptions) {
    if (!opts.method) throw new Error("http method is required");
    if (!opts.uri) throw new Error("uri is required");

    const rewrittenUrl = opts.uri.replace(
      "https://api.twilio.com",
      this.proxyBaseUrl.replace(/\/+$/, ""),
    );

    const response = await axios({
      url: rewrittenUrl,
      method: opts.method as Method,
      headers: opts.headers,
      auth:
        opts.username && opts.password
          ? { username: opts.username, password: opts.password }
          : undefined,
      params: opts.params ?? undefined,
      paramsSerializer: (params) =>
        qs.stringify(params, { arrayFormat: "repeat" }),
      data: opts.data
        ? qs.stringify(opts.data, { arrayFormat: "repeat" })
        : undefined,
      validateStatus: () => true,
    });

    return {
      statusCode: response.status,
      body: response.data,
      headers: response.headers,
    };
  }
}

const loopApiKey = process.env.LOOP_MESSAGE_API_KEY;
if (!loopApiKey) {
  throw new Error("LOOP_MESSAGE_API_KEY is required");
}

const client = twilio(loopApiKey, loopApiKey, {
  httpClient: new LoopProxyHttpClient("https://tw-api.loopmessage.com"),
});

async function sendMessage() {
  const message = await client.messages.create({
    from: "+17372222204",
    to: "+13231112233",
    body: "Hi there",
    // messagingServiceSid: "pool_id",  ## Optinal
    // mediaUrl: ["https://example.com/file.png"],  # Optional
    // WhatsApp:
    // from: "whatsapp:+17372222204"
    // to: "whatsapp:+13231112233",
  });

  return message.sid;
}

async function fetchMessageStatus(messageSid: string) {
  const message = await client.messages(messageSid).fetch();
  return message;
}