Verify Webhook

Now that you have an endpoint that is available on the internet, you want to be sure to verify and make sure that only requests coming from us are able to pass through into your systems and avoid dangerous actors. Using Convoy as our webhook provider, we’re able to support multiple verification means:

  • IP whitelist
  • Signature header validation

IP Whitelist

Requests/events from us can only come from these IP addresses:

  • 159.223.160.239
  • 147.182.169.205

With this, you block out every other request coming from other IPs, blocking out bad actors before hitting your endpoint at all.

Signature Header Verification

Requests from our systems are always signed with X-Bloc-Webhook; you can verify this request headers hitting your endpoint as seen below:

app.post("/my/webhook/url", function(req, res) {
    //validate event
    const hash = crypto.createHmac('sha256', webhook_secret).update(JSON.stringify(req.body)).digest('hex');
    if (hash == req.headers['X-Bloc-Webhook']) {
    // Retrieve the request's body
    const event = req.body;
    // Do something with event  
    }
    res.send(200);
});
<?php
// only a post with bloc signature header gets our attention
if ((strtoupper($_SERVER['REQUEST_METHOD']) != 'POST' ) || !array_key_exists('x-bloc-webhook', $_SERVER) ) 
    exit();

// Retrieve the request's body
$input = @file_get_contents("php://input");
define('BLOC_SECRET_KEY','SECRET_KEY');

// validate event do all at once to avoid timing attack
if($_SERVER['HTTP_X_BLOC_WEBHOOK'] !== hash_hmac('sha256', $input, BLOC_SECRET_KEY))
    exit();

http_response_code(200);

// parse event (which is json string) as object
// Do something - that will not take long - with $event
$event = json_decode($input);

exit();
?>
class BlocController < ApplicationController
  skip_before_action :authorize_request, only: :webhook

  def webhook
    bloc_instance = BlocObject.instance
    valid_event = bloc_instance.verify_webhook_event?(request)

    raise StandardError, 'Phony event - Not Bloc' unless valid_event
    render status: 200, plain: "Ok\n"

    body = params.permit!
    body = body.to_hash
    HandleBlocWebhookJob.perform_later(body)
  end
end

class BlocObject
  include Singleton

  WHITELISTED_IPS = ['159.89.231.210', '159.223.166.174', '159.65.239.138'].freeze

  def initialize
    @secret_key = ENV['BLOC_SECRET_KEY']
  end

  def verify_webhook_event?(request)
    verify_ip_address(request.remote_ip) && verify_header_signature(request)
  end

  private

  def verify_ip_address(ip_address)
    WHITELISTED_IPS.include?(ip_address)
  end

  def verify_header_signature(request)
    body = request.body.string
    hash = OpenSSL::HMAC.hexdigest('SHA512', @secret_key, body)
    hash == request.headers['HTTP_X_BLOC_WEBHOOK']
  end
end