Skip to main content

Message Verification

​ Note: in this document and elsewhere in the documentation we use the terms message, webhook and callback interchangeably.

All messages that Codept sends as part of the Merchant API and the Warehouse API contain a digital signature in the Authorization HTTP header.

In this section, we cover the details of this signature and share an example of the validation code that you can implement.

The purpose of the Authorization header#

The Authorization header is sent to enable you to confirm that a message truly comes from Codept. The header for a request is generated from two components:

  1. the shared secret that you receive during the Codept onboarding process;
  2. the data in the request.

Because Codept uses a shared secret in the encryption process, you can cryptographically validate if the request comes from Codept. We strongly recommend validating the Authorization header for each message you receive to ensure that you only process the webhooks that truly have been sent by Codept.

The format of the signature#

The Authorization header has the following format: ​ HMAC-SHA256 ${apiKey}:${nonce}:${timestamp}:${signature}

The fields of the header are the following:

  • HMAC-SHA256 is a literal string, it’s not a variable. This is to make it easy to detect the start of the signature in a string.
  • apiKey is a unique API key that you receive as part of the onboarding process, for example, 1000001. The purpose of this field is to let you know which API key and secret should be used to verify the signature.
  • nonce is a randomly generated UUID of the request, for example, ceef0a73-1566-47e1-8cfe-26aa71d5f11a.
  • timestamp is a UNIX timestamp from the moment when the request was signed, for example, 1591087751.
  • signature is a HMACSHA256 signature of the following string of text: ​
apiKey`\n`method`\n`path`\n`params`\n`nonce`\n`timestamp`\n`b64body

Note: If there is no params query string, Codept will use "null" as a value.

For example, for the following simple HTTP request" ​

POST /path?queryParam=1
Host: host
Content-Type: application/json
X-Requested-With: *
Content-Type: text/plain
​
{
"orderId": "orderId"
}

​ The method value is POST, the path is /path, the params string is queryParam=1, and the b64body is the base-64 encoded request body, ewogICAib3JkZXJJZCI6ICJvcmRlcklkIgp9. ​ Here is a complete example of a signature contained in the Authorization header:

HMAC-SHA256 1000001:ceef0a73-1566-47e1-8cfe-26aa71d5f11a:1591087751:JxEJExQIHR6GGygZvOF1ar/rsnMk6ki6w5aBOBEcTRA=

How to verify the signature#

To verify that the request came from Codept, compute the HMAC digest of the request and compare it to the value in the Authorization header. If they match, the webhook was sent from Codept.

Here is an example of computing a digest using Python and Flask:

from flask import Flask, request, abort
import hmac
import hashlib
import base64
​
app = Flask(__name__)
​
API_KEY = 1000001
API_SECRET = 'secret'
​
def verify_webhook(request, hmac_header):
alg, payload = hmac_header.split(' ')
if alg != "HMAC-SHA256":
abort(401)
api_key, nonce, timestamp, signature = payload.split(':')
if api_key != API_KEY:
abort(401)
message = str(api_key) + '\n' +
request.method + '\n' +
request.path + '\n' +
request.query_string + '\n' +
nonce + '\n' +
timestamp + '\n' +
base64.encode(request.get_data())
​
digest = hmac.new(API_SECRET.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).digest()
computed_hmac = base64.b64encode(digest)
​
return hmac.compare_digest(computed_hmac, signature.encode('utf-8'))
​
@app.route('/webhook', methods=['POST'])
def handle_webhook():
verified = verify_webhook(request, request.headers.get('Authorization'))
​
if not verified:
abort(401)
​
# process webhook payload
# ...
​
return ('Webhook verified', 200)