The Account Activity API is one of the most versatile APIs in the Twitter Developer Platform. With this API, people can allow your app to get notifications about their activity on Twitter. What’s best, it uses webhooks to deliver real time updates.

Developers can achieve a lot with the Account Activity API. For example, companies can use this API to get a sense of how the global conversation is unfolding around their products and services. A popular use case is with customer service, where your favorite brands can reply to your Direct Messages in real time, and determine the best next action. This usually involves using the Account Activity API, configuring a webhook, figuring out OAuth, and understanding how to send back a message using additional Twitter APIs.

This would sound like quite the task, if you didn’t have the right tools. Thankfully, Autohook is here to make things extremely easy for you.

Autohook is a Node.js module and a command line tool that sets up webhooks for you. This way, you can spend zero time figuring out how a webhook works and instead focus on building awesome things on Twitter.

Autohook makes things very easy — so easy that you can automate a DM conversation on Twitter in less than 100 lines of code. We’ll build a simple autoresponder that’ll greet whoever Direct Messages your user. Here’s what we’re going to do:

  • We’ll set up a project, including a Twitter app enabled with an Account Activity environment

  • We’ll set up Autohook, so it will do all the hard work for us

  • We’ll build the logic to only respond to incoming Direct Messages

  • We’ll add a way to show messages as read

  • We’ll show a typing indicator before sending the message

  • We’ll send back a sample autoresponder message

Your Twitter app

First things first: in order to use Twitter’s Account Activity API, you will need to create an app on Twitter, and give it the “Read, write, and Direct Messages” permission. You also need to have a valid development environment assigned to this app. Chances are you’ve already done this: if so, you should see something like this in your dev environments page:

If you don’t already have a dev environment created, simply click “Set up dev environment”, type a label and assign it to an app. The label can be anything, but make sure you remember it because you’re going to need it for later.

You will need to get your access tokens from your Twitter app dashboard. From the app that contains the environment you just created, click Details, then click “Keys and tokens”. Make a note of the values reported under:

  • API key

  • API secret key

  • Access token

  • Access token secret

Create a file in your home folder called .env.twitter, and add the values you found from the instructions above, plus your environment label:

 

  TWITTER_CONSUMER_KEY=API key 
TWITTER_CONSUMER_SECRET=API secret key 
TWITTER_ACCESS_TOKEN=Access token 
TWITTER_ACCESS_TOKEN_SECRET=Access token secret 
TWITTER_WEBHOOK_ENV=Your env label

Developers are often confused by consumer keys and access tokens. Long story short, think of those as if they were encrypted username and passwords. A consumer key/secret pair are identify your app, while access tokens are user credentials. This means your consumer key/secret do not change, but your access token/secret changes based on the user who’s authenticating with your app.

You’ve probably noticed that if you’re the owner of your own app, TWITTER_ACCESS_TOKEN and TWITTER_ACCESS_TOKEN_SECRET identify yourself. If that’s the case, you don’t really have to go through OAuth to identify yourself – we already generated those tokens for you (keep that in mind, it’s going to come in handy later).

Install Autohook

Next, we’ll install Autohook. Its package is available from both npm and Yarn.

npm i -S twitter-autohook

I almost wish there were more steps, but that’s it. Autohook is that easy!

Create a new project

We’ll start with a new project. We’re setting it up in your home folder, but naturally it can live anywhere:

mkdir ~/autohook-tutorial
cd autohook-tutorial

Let’s create a file named index.js. We’ll add code to call Autohook and make sure everything is configured as intended. Just so we can start with a clean environment, this code will remove all existing webhooks from your environment. If you don’t want to lose your existing webhooks, choose a different dev environment from your developer dashboard. As an alternative, you can remove the call to removeWebhooks(), and replace start() with startServer().

const { Autohook } = require('twitter-autohook');

(async start => {
  try {
    const webhook = new Autohook();
    
    // Removes existing webhooks
    await webhook.removeWebhooks();
    
    // Starts a server and adds a new webhook
    await webhook.start();
    
    // Subscribes to your own user's activity
    await webhook.subscribe({oauth_token: process.env.TWITTER_ACCESS_TOKEN, oauth_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET});  
  } catch (e) {
    // Display the error and quit
    console.error(e);
    process.exit(1);
  }
})();  

Save and run your file. You should see something similar to this (note that your URL will be different, and so will be your subscribing username):

  $ node index.js
Getting webhooks…
Removing webhooks…
Removing https://ce085a0d.ngrok.io/webhook…
Registering https://e3fd0ff6.ngrok.io/webhook as a new webhook…
Webhook created.
Subscribed to i_am_daniele's activities.

Behind the scenes, Autohook handled OAuth for us. It also ran a development server with logic to create a webhook, and to acknowledge signals sent by Twitter, including the one that triggers a CRC validation. And because we provided your access token/secret, it also subscribed your app to listen to your user’s activity (it did come in handy)!

What can go wrong at this point? The most common pitfall is that the authenticating user won’t authenticate. Remember, users need to authorize your app before it can access their activity; if you’re trying to authenticate a test user (or even better, if you’re asking a friend to help you test your app), make sure they’re authenticating using Sign In With Twitter and the 3-legged OAuth flow. Autohook’s got your back – you can find a sample implementation on the project’s page.

If you bump into an error, most likely your credentials are not set correctly; check your .env.twitter and try again. If you’re still stuck, come visit the Twitter Community forums and we’ll be more than happy to help you out!

Detect and filter incoming events

Now that Autohook took care of the underlying wiring, it’s time to focus on our app logic. The Account Activity API can ping your webhook with many activity types, so it’s important we only react to the activity type related to an incoming message.

Since you’re using Autohook, you can listen to an incoming event listener – The event name is just event. Add this right before the call to removeWebhooks():

    webhook.on('event', async event => {
      // Don't worry, we'll start adding something more meaningful
      // in just a moment.
      console.log('You received an event!');
    });

If you run your code now, nothing may seem to happen at first. But try and like a Tweet and something like this will show up in your Terminal:

  You received an event! { for_user_id: '102010879991606016',
  favorite_events:
   [ { id: '92274d54c83ff07669999a00cad3e835',
       created_at: 'Fri Aug 02 21:54:13 +0000 2019',
       timestamp_ms: 1564782853144,
       favorited_status: [Object],
       user: [Object] } ] }

Because your app subscribed to your user’s activities, we received that activity in real-time. The object always starts with for_user_id, which indicates a user ID (if you subscribe to multiple users, this is how you know which user this activity belongs to). The actual activity is described by a dictionary key. In this case, we received favorite_events as we just liked a Tweet. There are many activities, like tweet_create_events, follow_events, and direct_message_events. In Autohook, these values will all be event keys in your event object from the listener. We only want to listen to Direct Messages, so we’ll change our code to do so:

webhook.on('event', async event => {
      if (event.direct_message_events) {
        await sayHi(event);
      }
    });

In this case, we will only process Direct Message events, and we’ll send the details to a function named sayHi. This function will process the event and shoot a Direct Message back waving hi to any incoming Direct Message!

Say hi!

How do we know who’s the sender, and how can we be sure this message is directed to our account? Thankfully, the Direct Message event will contain full details about both parties involved in the conversation. You already have the OAuth credentials of the recipient (yourself), which is now going to send a message back by means of sayHi.

The logic for sayHi is straightforward:

  • We’ll check that the message is valid, and it’s directed to your user

  • We’ll only listen to incoming messages (outgoing messages generate an activity too, and we want to filter those out otherwise we’ll run into an infinite loop)

  • We’ll say hi!

To check that the message is valid, we’ll need to check that the message object exists and contains a message_create key. This key contains all the relevant details about the message, including the sender and recipient IDs, and the message itself. We’ll check the sender and recipient’s details, and if they’re the same, it means you are sending a message to yourself. If this happens, the autoresponder we’ll say hi to you, which in turn will cause the autoresponder to say hi to you, which in turn will cause the autoresponder to say hi to you… causing an infinite loop. It’s actually easier done than said:

 // We check that the message is a direct message
  if (!event.direct_message_events) {
    return;
  }

  // Messages are wrapped in an array, so we'll extract the first element
  const message = event.direct_message_events.shift();

  // We check that the message is valid
  if (typeof message === 'undefined' || typeof message.message_create === 'undefined') {
    return;
  }
 
  // We filter out message you send, to avoid an infinite loop
  if (message.message_create.sender_id === message.message_create.target.recipient_id) {
    return;
  }

All we have to do next is to prepare the request body for the message reply and send it using the Direct Messages API. The request body of this API has the exact same format of an Account Activity message response, which makes it easy to consume and produce.

  // Prepare and send the message reply
  const senderScreenName = event.users[message.message_create.sender_id].screen_name;

  const requestConfig = {
    url: 'https://api.twitter.com/1.1/direct_messages/events/new.json',
    oauth: oAuthConfig,
    json: {
      event: {
        type: 'message_create',
        message_create: {
          target: {
            recipient_id: message.message_create.sender_id,
          },
          message_data: {
            text: `Hi @${senderScreenName}! 👋`,
          },
        },
      },
    },
  };
  await post(requestConfig);

That’s it! Run your code and ask a friend to send you a message. You should see an incoming message, followed by an automated Direct Message:

Mark as read and typing indicator

Our code so far is about 80 lines of code, so we have plenty of room to implement all the fancy things an autoresponder can do. Direct Messages on Twitter can indicate when a message has been read (it’s the blue check next to the message timestamp). Since our autoresponder will read an incoming message for us, it would be nice to tell the sender our autoresponder read our message and that it’s about to reply.

Because the message body doesn’t have to be sophisticated, you will send these POST requests as form encoded, rather than sending raw JSON data like we did before. 

To get the read check mark, we’ll simply extract the message ID from the activity we received earlier. Just like before, we’ll create the appropriate request body to send it to the Mark Read endpoint:

async function markAsRead(messageId, senderId, auth) {
  const requestConfig = {
    url: 'https://api.twitter.com/1.1/direct_messages/mark_read.json',
    form: {
      last_read_event_id: messageId,
      recipient_id: senderId,
    },
    oauth: auth,
  };

  await post(requestConfig);
}

Similarly, we’ll now display a typing indicator which will present in the Direct Message window as a bubble with three dots. Since this is just an animation, there is no message attached to it; we will simply need to pass the ID of the person we want to show this bubble to.

async function indicateTyping(senderId, auth) {
  const requestConfig = {
    url: 'https://api.twitter.com/1.1/direct_messages/indicate_typing.json',
    form: {
      recipient_id: senderId,
    },
    oauth: auth,
  };

  await post(requestConfig);
}

Time to say bye

And there you have it! Because you didn’t have to deal with the complexity of webhooks and authentication, you may have noticed that you just wrote the entire code in less than 100 lines! This has been a quick and easy way to explore the power of the Account Activity API, and a great starting point for your next 100 lines of code. 

If you want to see the complete code in action, check out the Glitch app! Autohook is available via npm and Yarn, and the source code is available on Github.