Webhook Security

When registering a webhook listener with the v2 API it is recommended that you implement measures to verify the authenticity and origin of requests. To achieve this you can secure a webhook listener by registering a client key. We will use this client key to digitally sign the payloads that we send to your webhook listener; by verifying each webhook request signature you can be certain of the integrity of the request.

How do you register a client key?

You can register a client key via the following options:

  • When you create a new webhook you can pass the client key as a parameter to the request. See the API docs for "Create webhook".
  • You can update existing webhooks to use a client key. See the API docs for "Update webhook".

Note that you can also change an existing client key via the "Update webhook" API. Doing so will overwrite your old client key with the new one you provide.

How do you securely generate a client key?

Your client key should ideally be a secure and random string of characters. Such random strings can be generated in many programming languages via purpose-built libraries. An alternative is to generate them on the command line. In a Unix-like environment (which includes OS X) you could do the following:

pwgen -s 40 1
ruby -rsecurerandom -e 'puts SecureRandom.hex(20)'

What do we do with your client key?

We compute a hash using the HMAC SHA-1 algorithm, passing in your client key and request payload as arguments. The result is converted to a hexadecimal string and included in a request header named X-Ezypay-Signature.

How do you verify the signature?

To verify our signature you need to compute a hash using your client key and make sure it matches our signature. The steps on your end would be:

  1. extract the signature from the "X-Ezypay-Signature" HTTP header
  2. extract the payload from the HTTP request
  3. compute the hexadecimal HMAC SHA-1 passing in as arguments:
  • your client key
  • the request payload
  1. compare the resulting string with the signature you extracted in step 1.

What do we mean by "payload"?

The payload is the raw body of the HTTP request. That is, it's just JSON data and should be used "as is". Basically, don't parse and re-encode it or manipulate it in any way, just use the raw form.

Can I test the signature verification process?

Yes. The best way to do this is to compute a signature based on a simple key and payload to make sure that it matches our reference example. Our reference example will use the following inputs:

  • client key = "key"
  • payload = "some_payload_data"

The hexadecimal HMAC SHA-1 produces:

  • output = "c83f0f772795b95237c1da838fc602e070da3324"

Some code examples follow.

import org.apache.commons.codec.digest.HmacUtils;

public class Example {
  public String computeSignature(String clientKey, String payload) {
    return HmacUtils.hmacSha1Hex(clientKey, payload);
  }
}
using System.Security.Cryptography;

namespace Example {
    public class Signature {
        public string computeSignature(string clientKey, string payload) {
            byte[] clientKeyBytes = System.Text.Encoding.UTF8.GetBytes(clientKey);
            byte[] payloadBytes = System.Text.Encoding.UTF8.GetBytes(payload);
            
            HMACSHA1 hmac = new HMACSHA1(clientKeyBytes);
            byte[] hashValue = hmac.ComputeHash(payloadBytes);
          
            return BitConverter.ToString(hashValue).ToLower().Replace("-", "");
        }
    }
}
function computeSignature(clientKey, payload) {
  var crypto = require('crypto');
  var hmac = crypto.createHmac('sha1', clientKey).update(payload);
  return hmac.digest('hex');
}
import hashlib
import hmac

def computeSignature(clientKey, payload):
  utf8 = "utf-8"
  clientKeyBytes = bytes(clientKey).encode(utf8)
  payloadBytes = bytes(payload).encode(utf8)
  computedHmac = hmac.new(clientKeyBytes, payloadBytes, digestmod=hashlib.sha1)
  
  return computedHmac.digest().encode("hex")
<?php
  function computeSignature($clientKey, $payload) {
    return hash_hmac('sha1', $payload, $clientKey, false);
  }
?>
require 'openssl'

def computeSignature(clientKey, payload)
  digest = OpenSSL::Digest.new('sha1')
  return OpenSSL::HMAC.hexdigest(digest, clientKey, payload)
end
use Digest::HMAC_SHA1;

sub compute_signature {
  my ($clientKey, $payload) = @_;
  my $hmac = Digest::HMAC_SHA1->new($clientKey);
  $hmac->add($payload);
  
  return $hmac->hexdigest;
}

What if you don't want to use a client key?

This security feature is optional. If you don't register a client key with a webhook then we won't compute the signature and won't include it as a header in the HTTP request.