Secure Your Twilio Account: Monitoring and Alerts for Suspicious Activities

March 31, 2025
Written by
Matt Coser
Twilion
Reviewed by
Christopher Konopka
Contributor
Opinions expressed by Twilio contributors are their own
Paul Kamp
Twilion

As telecom criminals evolve, businesses need to evolve with them to secure communications and protect against increasingly sophisticated attacks. Twilio has a suite of products and APIs which help you build communications experiences, but we also offer powerful tools that provide real-time insights and facilitate proactive threat detection.

In this blog post, we'll explore how to harness our APIs to set up robust monitoring and alerts, enhancing your security posture and helping you stay one step ahead of potential threats.

Prerequisites

The code examples in this post are provided for demonstration purposes. However, if you wish to follow along, you should have the following ready:

APIs and Features

Twilio Account activity can be broken down into three basic categories:

  • Usage - Twilio products used/consumed by your application
  • Events - Actions performed using a Twilio Account SID or Console User
  • Alerts - Errors and Warnings that occur as a result of Usage and Events activity.

These are used in concert to keep track of what is happening behind the scenes of your application.

Usage Records API

Message segments, Programmable Voice Minutes, Phone Numbers, Lookups, and more are all tracked and can be reviewed via the Usage Record API.

The results can be fed to your own reporting or analytics tool for tracking, or you can use a Helper Library to make your own reports and scripts.

A GET to /Usage/Records/Today.json will produce all the usage from the current day.

import os
from twilio.rest import Client
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
client = Client(account_sid, auth_token)
last_months = client.usage.records.last_month.list(limit=20)
for record in last_months:
    print(record.category)

Twilio provides multiple other time based endpoints for Usage Records:

  • /Usage/Records/AllTime.json
  • /Usage/Records/Daily.json
  • /Usage/Records/LastMonth.json
  • /Usage/Records/Monthly.json
  • /Usage/Records/ThisMonth.json
  • /Usage/Records/Yearly.json
  • /Usage/Records/Yesterday.json

Usage Triggers

Twilio can send an email or webhook when certain defined usage related conditions are met.

Usage Triggers can be set up in the Console.

  1. Enter the Usage Trigger properties.
  2. Determine which action should be taken when the trigger value is exceeded.
  3. Click Save!
Interface for setting up a new trigger with fields for name, category, value, conditions, and actions.

There is also an API for that. To create a trigger, POST to /Usage/Triggers with your desired parameters. The CallbackUrl is what Twilio will hit when the usage threshold is reached.

curl -XPOST https://api.twilio.com/2010-04-01/Accounts/ACxxxxx/Usage/Triggers.json \
    -d "TriggerValue=2" \
    -d "UsageCategory=calls-inbound" \
    -d "CallbackUrl=http://myapp.com/inboundUsage/" \
    -u ACxxxxx:your_auth_token'

When the usage value is exceeded, the webhook result should look something like this:

{
   "usage_record_uri": "/2010-04-01/Accounts/ACxxxxx/Usage/Records.json?Category=sms", 
   "date_updated": "Sat, 13 Oct 2012 21:32:30 +0000", 
   "date_fired": null, 
   "friendly_name": "Trigger for sms at usage of 1000", 
   "uri": "/2010-04-01/Accounts/ACxxxxx/Usage/Triggers/UTxxxxx.json", 
   "account_sid": "ACxxxxx", 
   "callback_method": "POST", 
   "trigger_by": "usage", 
   "sid": "UTxxxxx", 
   "current_value": "57", 
   "date_created": "Sat, 01 Jan 2025 21:32:30 +0000", 
   "callback_url": "http://www.example.com", 
   "recurring": null, 
   "usage_category": "sms", 
   "trigger_value": "1000.000000"
}

Debugger Webhook

In Console, users can define a Debugger Webhook that Twilio will hit whenever an Error or Warning occurs on your account.

Screenshot showing webhook endpoint payload example with columns for property and description.

The application receiving the webhook from Twilio will be hit with every Error and Warning, so specific logic should be included to handle various cases. This node/express example route can be used to handle Invalid CallerID and Invalid Phone Number Format errors differently than everything else.

app.post('/debugger', (request, response) => {
    try {
        const payload = JSON.parse(request.body.Payload);
        const errorCode = payload.ErrorCode;
        switch (errorCode) {
            case '13214':
                console.log('Handling error 13214: Invalid callerId');
                // Add specific logic for error 13214
                break;
            case '13223':
                console.log('Handling error 13223: Invalid phone number format.');
                // Add specific logic for error 13223
                break;
            default:
                console.log('Error code:', errorCode);
                // Logic for other error codes
                break;
        }
        response.sendStatus(200);
    } catch (error) {
        console.error('Error processing request:', error);
        response.sendStatus(500);
    }
});

Console Alarms

Users can define different webhooks for specific warnings or errors, offering a more flexible alternative to the Debugger Webhook. Multiple alarms can be created to handle different Errors or Warnings with different behavior. Additionally, users can define different methods of how the Alarm will notify them - webhook, email, console notification, or all of the above.

Interface for creating an alarm based on error codes with options to define threshold, notification, and name.

Monitor Alerts API

The Debugger Webhook and Console Alarms are designed to actively notify you of an Error or Warning as it occurs in real time. The Monitor Alerts API is used to retrieve historical information on these Errors and Warnings, after they occur.

This example will return details on errors that occurred in 2025 so far.

import os
from twilio.rest import Client
from datetime import datetime
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
client = Client(account_sid, auth_token)
alerts = client.monitor.v1.alerts.list(
    log_level="warning",
    start_date=datetime(2025, 1, 1, 0, 0, 0),
    limit=20
)
for record in alerts:
    print(record.account_sid)

This example produces a bar graph with a total count of each error code in the defined time period. First, the alerts that have occurred this year are listed via the API. The results are then parsed and each unique error code is counted and plotted.

import matplotlib.pyplot as plt
from collections import Counter
from twilio.rest import Client
from datetime import datetime
# Initialize Twilio client
client = Client(‘ACxxxx’, ‘abc123’)
# Fetch events
events = client.monitor.v1.alerts.list(start_date=datetime(2025, 1, 1, 0, 0, 0)
# Filter and count error codes
error_codes = [event.error_code for event in events]
print(error_codes)
error_count = Counter(error_codes)
print(error_count)
# Prepare data for plotting
codes, counts = zip(*error_count.items())
# Plotting
plt.figure(figsize=(10, 6))
plt.bar(codes, counts, color='skyblue')
plt.xlabel('Error Code')
plt.ylabel('Total Occurrences')
plt.title('Total Errors per Error Code')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

Visualizing this data could be very useful for establishing a baseline of error counts for more effective anomaly detection.

15003 with 13 errors and 11200 with 10 errors.

Monitor Events API

The Events platform offers detailed logging and change-tracking for all Twilio resources, like provisioning phone numbers and modifying security settings. The Monitor Events Resource allows you to access this log and can be integrated with your existing log management solution. This API is commonly used for periodic monitoring, post mortem investigations, and building a baseline of expected activity.

import os
from twilio.rest import Client
from datetime import datetime
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
client = Client(account_sid, auth_token)
events = client.monitor.v1.events.list(
    start_date=datetime(2025, 1, 1, 0, 0, 0),
)
for record in events:
    print(record.account_sid)

Twilio offers numerous Event Types which are available to audit via the Console and API.

Event Streams

The Event Streams REST API gives access to a flow of all interactions sent or received on Twilio. It allows streaming to multiple destinations, such as Twilio Segment, Amazon Kinesis, or webhooks, and includes features like consistent metadata, event queuing, and delivery from various Twilio products.

Setting up an HTTP sink is as easy as making a POST to /Sinks.

import os
from twilio.rest import Client
# Find your Account SID and Auth Token at twilio.com/console
# and set the environment variables. See http://twil.io/secure
account_sid = os.environ["TWILIO_ACCOUNT_SID"]
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
client = Client(account_sid, auth_token)
sink = client.events.v1.sinks.create(
    description="My Kinesis Sink",
    sink_type="kinesis",
    sink_configuration= { 
        "destination": "http://example.org/webhook",
        "method": "<POST|GET>"
    }
)
print(sink.description)

Test Cases

With a firm understanding of Twilio’s various Monitoring and Usage related APIs and features, we can apply these concepts to real situations.

Account Takeover (ATO)

An Account Takeover is when a bad actor assumes control of online accounts owned by other people. With control over your Twilio account, a bad actor can send malicious emails, make expensive calls, or launch smishing campaigns at your expense.

The best way to avoid an ATO is to keep your Twiliio account secure.

  • Use strong passwords
  • Protect your credentials
  • Implement periodic rotation where applicable
  • Use roles and permissions specific to jobs/ tasks

See the Anti Fraud Developers guide for more info on developing with security in mind.

However, prevention is only part of the equation. You should consistently monitor for unauthorized usage, changes to services, and other suspicious activity on your account, and compare them to a normal baseline to identify potential issues quickly.

Auth Token/ API Key Rotation

One of the first things a bad actor might do when they gain access to an account is to change the credentials so the original owner has a harder time stopping the malicious activity.

If your Auth Token is rotated without your knowledge, requests will begin to fail. Keep track of these errors by setting an Alarm. The 20003 Error is generated when your Account SID is used to make an API request with an invalid Auth Token.

Configuration screen for creating an alarm when 20003-Permission Denied error code occurs.

In the event a subaccount is compromised, the Parent account credentials can be used to scrape the Monitor Events Resource for auth token related events. Something like this can be periodically run as a cron job to notify an admin via SMS if unexpected auth token rotations have occurred.

from twilio.rest import Client
from datetime import datetime, timedelta, timezone
account_sid = ‘’
auth_token = ‘’ 
sub_account_sid = ‘’
from_number = '+12345678901
to_number = '+19876543210
client = Client(account_sid, auth_token, sub_account_sid)
# Specify the dates in GMT and ISO 8601 format.
end_date = datetime.now(timezone.utc) #
start_date = end_date - timedelta(days=1)
events = client.monitor.v1.events.list(start_date=start_date, end_date=end_date)
auth_token_events = [event for event in events if 'auth-token' in event.event_type]
if auth_token_events:  
    client.messages.create(
        body=f"Alert: {len(auth_token_events)} auth-token related events detected in the past week.",
        from_=from_number,
        to=to_number
    )
    print("SMS sent regarding auth-token related events.")
else:
    print("No auth-token related events detected.")

Changes in Activity

Being familiar with the expected behaviors of your app, even during extreme conditions, is extremely important when trying to detect suspicious activity. If your Twilio app does not utilize Lookups, Lookup usage may be a sign of an attacker gathering information on potential targets. This Usage Trigger will shoot a webhook every day where at least 1 Lookup was performed.

curl -XPOST https://api.twilio.com/2010-04-01/Accounts/ACxxxxx/Usage/Triggers.json \
    -d "TriggerValue=1" \
    -d "UsageCategory=lookups" \
    -d “Recurring=daily” \
    -d "CallbackUrl=http://test-app.com/lookup_trigger_callback/" \
    -u ACxxxxx:your_auth_token'

Toll Fraud/ Toll Pumping

Toll Fraud, Traffic Pumping, Call Pumping, International Revenue Share Fraud (IRSF) are all different names for the same basic concept - fraudsters generate calls to premium-rate numbers they own to generate income from the call charges. If an ATO takes place, the bad actor may begin to attempt Toll Pumping at your expense. Similarly, SMS Pumping is when bad actors send messaging traffic to numbers they control, for a share of the generated revenue.

In V2 of the Lookup API, Twilio provides the SMS Pumping Risk Score to determine a particular phone number’s history and help prevent fraud.

For Verify customers using the SMS channel, Fraud Guard is enabled by default at no additional cost. When creating your Verify Service, you will have the opportunity to set a Protection level based on your risk appetite. Twilio establishes a baseline of expected activity to help identify unusual traffic patterns and filters out fraudulent activity.

SMS Fraud Guard settings for Twilio Verify

Monitoring your account activity and alerting on strange or unexpected behavior can further help you detect and act on malicious behavior before it spirals out of control.

Geo Permissions

Twilio offers location based permissions for communications using Programmable Voice, Elastic SIP Trunking, Programmable Messaging, and Verify. Taking the time to set Geo Permissions based on expected activity, and auditing them periodically, are crucial security measures to keep in mind when building. For example, if your application is designed for users in certain locations, it is likely safe to disable other locations entirely.

Once access to an account is gained, Toll Pumpers will sometimes try to open a bunch of high risk geo permissions before initiating calls and messages.

This example scrapes Monitor Event Resource and produces a line graph to visualize call volume over time.

import pandas as pd
from twilio.rest import Client
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
# Initialize Twilio client
client = Client(account_sid, auth_token)
start_date = datetime(2025, 2, 1, 0, 0, 0)
# Fetch call logs
call_logs = client.calls.list(start_time_after=start_date)
call_data = [{'date': call.start_time.date()} for call in call_logs]
call_df = pd.DataFrame(call_data)
# Group by date and count calls
daily_calls = call_df.groupby('date').size().reset_index(name='total_calls')
# Fetch event data
events = client.monitor.v1.events.list(start_date=start_date)
geo_events = {event.event_date.date() for event in events if 'voice-geographic-permissions' in event.event_type}
# Plotting
plt.figure(figsize=(10, 6))
plt.plot(daily_calls['date'], daily_calls['total_calls'], marker='o', label='Total Calls')
# Highlight days with geo-permission events
for geo_date in geo_events:
    print(geo_date)
    print(daily_calls['date'].values)
    plt.axvline(geo_date, color='red', linestyle='--', label='Geo-Permission Event')
plt.xlabel('Date')
plt.ylabel('Total Number of Calls')
plt.title('Total Calls Per Day with Geo-Permission Events')
plt.xticks(rotation=45)
plt.grid(True)
# Handle legend to avoid duplicate entries
handles, labels = plt.gca().get_legend_handles_labels()
unique_labels = dict(zip(labels, handles))
plt.legend(unique_labels.values(), unique_labels.keys())
plt.tight_layout()
plt.show()

In the resulting graph, the red lines indicate at least one voice-geographic-permission event took place on that date, and the blue line tracks the number of daily calls.

In this hypothetical example, we can see a steady increase of calls since the geo-permission event on March 1.

Line graph showing total calls per day with two geo-permission events marked in red.

Additionally, you may opt to set up debug Alerts for the Geo Permission related Error Codes.

Or, capture Call related Events in an HTTP sink, and analyze the country codes. Subscribing to the com.twilio.voice.api-request.call.created event will allow you to analyze every call that is created by POSTing to your Account’s /Calls resource.

SINK_CONFIGURATION_OBJ=$(cat << EOF
{
    "destination": "http://example.org/webhook",
    "method": "<POST|GET>"
}
EOF
)
curl -X POST "https://events.twilio.com/v1/Sinks" \
--data-urlencode "Description=My Kinesis Sink" \
--data-urlencode "SinkType=kinesis" \
--data-urlencode "SinkConfiguration=$SINK_CONFIGURATION_OBJ" \
-u ACxxxxx:auth_token

Event Streams offers an extensive list of event types to tune your monitoring and alerting. You can also request a new Event Type if the one you need isn’t listed.

Traffic Increase

A sudden increase in call or SMS traffic can be a wonderful thing! Maybe a recent campaign took off, or business is booming after the launch of a great new widget. It could also be a sign of trouble. Telecom criminals expect to be discovered in short order, and will move to their next target. So, once they gain control of an account they often act fast and make large numbers of calls in a short amount of time.

Use the Usage Records API to establish a baseline of activity for your application. Then, Usage Triggers can be implemented to let you know if this baseline is exceeded. It is important to continuously monitor your account and subaccount usage for any unwanted abuse.

This example scrapes Call Logs and determines the average number of calls over a given period of time, and creates a Usage Trigger for when the amount exceeds 20% of the average. Something like this can be implemented as a cron job to update Usage Trigger dynamically as your natural traffic patterns ebb and flow.

Basically, when usage is exceeded a call will be made with a notification explaining the details.

from twilio.rest import Client
from datetime import datetime, timedelta, timezone
import math
account_sid = ‘’
auth_token = ‘’
client = Client(account_sid, auth_token)
# Specify the dates in GMT and ISO 8601 format
end_date = datetime.now(timezone.utc)
start_date = end_date - timedelta(weeks=1)
call_logs = client.calls.list(start_time_after=start_date, start_time_before=end_date)
# Calculate average number of calls per day
call_counts = {}
for call in call_logs:
    call_date = call.start_time.date()
    if call_date in call_counts:
        call_counts[call_date] += 1
    else:
        call_counts[call_date] = 1
num_days = (end_date - start_date).days
# Calculate the average calls per day
average_calls_per_day = sum(call_counts.values()) / num_days
trigger_threshold = math.ceil(average_calls_per_day * 1.2)
# Create the Usage Trigger
trigger = client.usage.triggers.create(
    callback_url='https://function.url/voice-alert',
    recurring='daily',
    trigger_value=str(trigger_threshold),
    usage_category='calls',
    trigger_by='count',
    friendly_name='Call Volume Exceeds Avg'
)
print(f"Usage Trigger created for: {trigger_threshold} calls.")

The callback_url is a Function containing the following code, which will send an outbound call when triggered. The URL is a TwiML Bin containing a <Say> message to be played when the recipient picks up. Mustache templates can be used with TwiML bins to produce dynamic messages.

exports.handler = function (context, event, callback) {
  const twilioClient = context.getTwilioClient();
  twilioClient.calls
    .create({
    from: "+12345678901",
    to: "+19876543210",
    url: "https://handler.twilio.com/twiml/EHxxxx",
  })
    .then((call) => {
      console.log('Call successfully placed');
      console.log(call.sid);
      return callback(null, `Success! Call SID: ${call.sid}`);
    })
    .catch((error) => {
      console.error(error);
      return callback(error);
    });
};

From there credentials can be rotated, Geo Permissions can be altered, or other remediation efforts can be taken per your incident response plan.

Messaging Intelligent Alerts

Twilio uses Machine Learning/AI to identify anomalies in your outbound messaging traffic by analyzing multiple data points like error codes and message volume. Intelligent Alerts are similar to Alarms, but are handled by Twilio instead of set manually via the Console or API.

Screenshot of the Intelligent Alerts data monitoring system dashboard showing anomaly detection results.

DDoS/Server Side Hacking

While the Twilio side of your app may be locked up, the provider hosting your app’s logic may not be. In the event your server is compromised or otherwise unavailable, Fallback URLs will come in handy.

The most common use of a fallback URL is when the primary handler for an incoming call or SMS fails. To maintain continuity you can duplicate your application logic, or choose to provide temporarily scaled down backup services via this secondary URL.

Screenshot of voice configuration settings showing various options for call handling and routing.

Fallback URLs can also be utilized when making an outbound call, in case the primary URL that provides the TwiML instructions fails.

curl -X POST "https://api.twilio.com/2010-04-01/Accounts/ACxxxxx/Calls.json" \
--data-urlencode "Url=https://primary-app.com/voice.xml" \
–data-urlencode “FallbackUrl=https://backup-app.com/fallback.xml
--data-urlencode "To=+15558675310" \
--data-urlencode "From=+15017122661" \
-u ACxxxxx:auth_token

If you don’t have backup or alternate hosting set up yet, set Alarms (or set logic in your Debugger Webhook) to keep track of HTTP related Errors which may be a sign that your app is not functioning as expected. This Alarm will send an Email and fire a Webhook after detecting over ten 11200 Errors per hour.

Image showing steps to set a target metric, activation threshold, and notification method for an alarm system.

See our docs for more on availability and reliability for more on building with contingencies in mind.

Conclusion

Twilio's Usage and Monitor APIs for monitoring and alerting can help your business maintain robust oversight of your communications infrastructure. By tracking usage patterns and account activity, you can identify and address anomalies then react appropriately. Moreover, the ability to customize alerts and integrate with existing reporting and SIEMs enhances responsiveness and supports your informed decision-making.

Found my post interesting? Please take a moment to browse these additional resources: