Webhook Authentication
Overview
This guide explains the available authentication methods that you can use to protect your Webhook endpoints.
Each Webhook endpoint has a secret and an Authentication Type.
The Authentication Type dictates the HTTP Header Authorization that we will send to your services.
You can choose the Authentication Type between: HMAC SHA1 (this the legacy authentication), Basic Auth, Bearer Token and None (no Authorization header).
In addition to the Authorization HTTP header, we also send in EVERY request a X-HMAC-SHA256 header which contains the SHA-256 signature of the request body using your Webhook endpoint's secret.
HMAC SHA256 Signature
Every webhook request is signed with HMAC-256, this signature is present in the HTTP Header X-HMAC-SHA256.
We STRONGLY advise you to protect your service by validating this signature.
The HMAC SHA256 signature ensures that the request was sent by us and also ensures the integrity of the message. Plus, since only the computed hash is present in the HTTP request, if an attacker intercepts the request, they will only be able to replay the SAME request and won't be able to forge new ones.
Example of HTTP Headers (SHA256)
Headers from a request using the Bearer Token Authentication type.
Please, notice the x-hmac-sha256 header, this example shows that you
will receive this header independent of the selected Authentication Type.
content-length: 298
x-hmac-sha256: PLZ05+ixPce3G/cKhiausM7ZGbmpISyzcnP0ivaPju4=
authorization: Bearer token123
content-type: application/json
Computing and validating the hash (SHA256)
The HMAC SHA256 is computed using the following algorithm:
- Encode the request body and the secret to UTF-8
- Compute the HMAC SHA256 hash using previous values, this will give you a byte-array
- Encode the byte-array to base64 and decode the result to UTF-8
- The result will be a string with the base64 encoding of the HMAC SHA256 signature
You can always get the Webhook secret for your endpoint in the DeveloperPortal.
To validate the hash:
- Compute the hash using the method above
- Extract the hash from the header
X-HMAC-SHA256 - Compare both strings
Bellow you will find reference implementations:
Python
import base64
import hmac
import hashlib
def compute_hash(webhook_secret: str, payload: str, algorithm = hashlib.sha256) -> str:
webhook_secret_bytes = webhook_secret.encode('UTF-8')
payload_bytes = payload.encode('UTF-8')
computed_hash_bytes = hmac.new(webhook_secret_bytes, payload_bytes, algorithm).digest()
computed_hash_base64_bytes = base64.encodebytes(computed_hash_bytes)
return computed_hash_base64_bytes.decode('UTF-8').strip()
def validate(webhook_secret: str, payload: str, hash_signature: str, algorithm = hashlib.sha256) -> bool:
computed_hash = compute_hash(webhook_secret, payload, algorithm)
return computed_hash == hash_signature
Java
package auth.hmac;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Objects;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public final class HmacSigner {
public static final HmacSigner HMAC_SHA1 = new HmacSigner("HmacSHA1");
public static final HmacSigner HMAC_SHA256 = new HmacSigner("HmacSHA256");
private final String algorithm;
public HmacSigner(String algorithm) {
this.algorithm = algorithm;
}
public String computeHash(final String secret, final String payload) {
try {
final Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), algorithm));
byte[] bytes = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(String.format("Algorithm %s does not exist", algorithm), e);
} catch (InvalidKeyException e) {
throw new RuntimeException("Key is invalid.", e);
}
}
public boolean validate(final String secret, final String payload, final String hashSignature) {
final String computedHash = computeHash(secret, payload);
return Objects.equals(hashSignature, computedHash);
}
}
JavaScript
const crypto = require('crypto');
function computeHash(webhookSecret, payload, algorithm = 'sha256') {
const webhookSecretBytes = Buffer.from(webhookSecret, 'utf-8');
const payloadBytes = Buffer.from(payload, 'utf-8');
const computedHashBytes = crypto.createHmac(algorithm, webhookSecretBytes).update(payloadBytes).digest();
const computedHashBase64Bytes = computedHashBytes.toString('base64');
return computedHashBase64Bytes.trim();
}
function validate(webhookSecret, payload, hashSignature, algorithm = 'sha256') {
const computedHash = computeHash(webhookSecret, payload, algorithm);
return computedHash === hashSignature;
}
C#
using System;
using System.Security.Cryptography;
using System.Text;
public class HmacSigner
{
public string ComputeHash(string secret, string payload)
{
byte[] webhookSecretBytes = Encoding.UTF8.GetBytes(secret);
byte[] payloadBytes = Encoding.UTF8.GetBytes(payload);
using (var hmac = new HMACSHA256(webhookSecretBytes))
// using (var hmac = new HMACSHA1(webhookSecretBytes())
{
byte[] hashBytes = hmac.ComputeHash(payloadBytes);
return Convert.ToBase64String(hashBytes);
}
}
public static bool Validate(string secret, string payload, string hashSignature)
{
string computedHash = ComputeHash(secret, payload);
return computedHash.Equals(hashSignature);
}
}
PHP
<?php
function computeHash(string $webhookSecret, string $payload, string $algorithm = 'SHA256') : string {
$webhookSecretBytes = utf8_encode($webhookSecret);
$payloadBytes = utf8_encode($payload);
$computedHashBytes = hash_hmac($algorithm, $payloadBytes, $webhookSecretBytes, true);
$computedHashBase64Bytes = base64_encode($computedHashBytes);
return utf8_decode($computedHashBase64Bytes);
}
function validate(string $webhookSecret, string $payload, string $hashSignature, string $algorithm) : bool {
$computedHash = computeHash($webhookSecret, $payload, $algorithm);
return $computedHash === $hashSignature;
}
Hints
This online tool is a good place to validate your implementation. Just ensure you select the output format as base64.
Legacy Authentication: HMAC SHA1
The legacy authentication type is a HMAC SHA1 of the request body and the Webhook's secret. It sends in every request an HTTP Header with this format: Authorization: MAC {HASH}.
SHA1 is a weak cryptographic algorithm, so we don't recommend integrations to use this method. Please, consider using the HMAC SHA256 instead.
Computing and validating the hash (SHA1)
To compute the hash you can use the same code from the HMAC SHA256, just change the algorithm to hmac.sha1.
To validate the request, extract the hash from the header Authorization. IMPORTANT:
Remember to remove the MAC prefix from the header and always strip your strings.
Example of HTTP Headers (SHA1)
x-hmac-sha256: TLUkvaPA7J+FWuQXDwcgnLa84WuHp526pCt4I6FgsXk=
authorization: MAC LblnjLxrJr40CDcM44+cvM/dYlk=
Basic Auth
If you use this Authentication Type, you will need to provide a username and password in the endpoint configuration in the Developer Portal. You can get more details about basic auth here.
Webhooks with Basic Auth will have this HTTP Header: Authorization: Basic base64({username}:{password}).
If an attacker intercepts a request using Basic Auth, they will immediately have access to the user and password. From this point, the attacker will be free to simulate any request they want.
For this reason, we strongly suggest using the
HMAC SHA256.
Validating (Basic Auth)
- Extract the
Authorizationheader value - Remove the
Basicprefix - Decode the value using base64
- Split the string in the first
:, the first value will be the username and the remaining the password - Validate the username and password with value stored locally in your application
Example of HTTP Headers (Basic Auth)
x-hmac-sha256: TLUkvaPA7J+FWuQXDwcgnLa84WuHp526pCt4I6FgsXk=
authorization: Basic dGVzdGU6dGVzdGU=
Bearer Token
Very similar to Basic Auth but uses a user defined token instead of username and password. You are free to use anything that makes sense for your application as a token, but a JWT is a good approach.
If an attacker intercepts a request using Bearer Token, they will immediately be able to do other requests using the token.
For this reason, we strongly suggest using the
HMAC SHA256.
Validating (Bearer Token)
- Extract the
Authorizationheader value - Remove the
Bearerprefix - Compare the token with your internal one
Example of HTTP Headers (Bearer Token)
x-hmac-sha256: TLUkvaPA7J+FWuQXDwcgnLa84WuHp526pCt4I6FgsXk=
authorization: Bearer this.is.a.token