Validating the Webhook Signature Header in Node.js

beta
verification
webhooks
account-activity

#1

What is expected Content-Type for the body of a POST request from the Account Activity API for webhooks? I do not see documentation on it in the Validating the Signature Header

I am having difficulty with assuming the type is JSON(application/json) for getting my HMAC hashes created and compared properly. I’ve tried Content-Type Text with no luck. I am using Postman to reproduce a POST with X-Twitter-Webhooks-Signature Header and body that I logged from a tweet_create_events I received earlier. Anything in particular I have to do in the way I paste the JSON string of a message event?

@phuson mentions getting the raw string from the body yields different results.

The Node.js crypto module does not include a compare_digest for HMAC like the one found in the python library. Is there anything in specific that needs to be done in node and express to validate the signature header?

Some sort of validate_signature function in the twitterdev/twitter-webhook-boilerplate-node repo would be helpful. @andypiper @joncipriano

Here is a minimal example I made based on the boilerplate repo. Does something need to change in bodyParser for it to work?

require('dotenv').config();
var crypto = require('crypto');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/path/to/twitter/webhook', function(request, response) {
  var twitterSignature = request.headers['X-Twitter-Webhooks-Signature'] ||
                         request.headers['x-twitter-webhooks-signature'];
  
  if(validTwitterSignature(twitterSignature, request.body)){
    response.sendStatus('200');
    event_processor.process(request.body);
  }else{
    response.sendStatus('404');
  }

})

var validTwitterSignature = function(signature, body){

    var generatedSignature = 'sha256='.concat(
      crypto.createHmac('sha256', process.env.TWITTER_CONSUMER_SECRET)
      .update(JSON.stringify(body),'utf8')
      .digest('base64')
    );
    
    return signature === generatedSignature;
}

#2

There is a Validate Webhook option in the (newer) Account Activity Dashboard app we’ve published, in case that is helpful?


#3

The webhook api is currently working great and my get_challenge_response works great too.

How do I prevent anyone other than Twitter from POSTing to my webhook endpoint. What do I have to do differently in node to Validate the Signature Header ?

Here is an example of validating the Signature Header in Python ranman/WhereML

def verify_request(event, context):
    crc = event['headers']['X-Twitter-Webhooks-Signature']
    h = hmac.new(bytes(CONSUMER_SECRET, 'ascii'), bytes(event['body'], 'utf-8'), digestmod=sha256)
    crc = b64decode(crc[7:]) # strip out the first 7 characters
    return hmac.compare_digest(h.digest(), crc)

My question is: what is expected Content-Type for the body of a POST request from Twitter to my webhook URL? What configurations need to be set for the body-parser module so that creating an HMAC string can be valid? Using bodyParser.json, bodyParser.raw, bodyParser.text? Any text manipulation I need to do that I’m missing?


#4

Edit: I misread your question but I’ll leave this code here for others.

Here’s what we’re doing and this has worked as a valid CRC check for the better part of a year.