Webhook Signature

Intro

The event sent to your endpoint will carry a signature in the query string like below:

http://your-endpoint/?sign={signature}

Pesudo-code

The signature is used to verify the event is sent by us, and the body is meant by us.

We are using a standard HMAC-SHA256 keyed hash to generate this signature. The signature is created by combining a shared secret (your app secret) with the body of the request we're sending. Be aware that the process is deterministic and you must follow the steps below to produce the same signature.

The following pesudo-code illustrate the process of the generation

# step 1 - get all the messages
const appSecret = {your_app_secret}
const timestamp = req.header['x-shopline-developer-event-timestamp'] // obtain it from request header
const payload = req.body // obtain it from request body

# step 2 - serialization
const sortedPayload = sortByKeys(req.body) // sort the payload, see below section for sorting the payload
const stringifyPayload = JSON.stringify(sortedPayload); // stringify it
const message = timestamp + ":" + stringifyPayload // e.g. "1837193717:{message_string}"

# step 3 - generate signature
const hash = crypto
  .createHmac('sha256', appSecret) // use app secret as key to create hmac
  .update(message) // add the message
  .digest('hex'); // generate disgest

Sorting the payload

Payload is in json format and should be sorted by its key alphabetically, see the following input-output example.

# Original json
{
  "b": 1,
  "c": { c: 1, b: 2, a: 3},
  "a": 3
}
# Sorted json
{
  "a": 3
  "b": 1
  "c": {
      a: 3
      b: 2
      c: 1
  }
}

Known issues

Different programming languages handle string differently. Due to differences in how various programming languages and libraries handle strings, it is crucial to understand the impact.

For example, two libraries have unique ways of handling strings, especially when it comes to special characters. This can lead to discrepancies when stringifying JSON payloads, particularly if special characters are not correctly escaped or transformed.

  1. Understand Your Environment
    Before starting, identify the programming language and libraries (JSON serialization) you are using may differ compared to SHOPLINE (Please feel free to contact us if you have any questions)
  2. Identify Special Characters
    Recognize which special characters may cause issues in your environment
  3. Adding Unescaping Logic
    Identify any improperly escaped characters, then replace them with their correctly escaped versions
  4. Webhook Signatures
    Pay special attention to webhook payloads, as improper handling of special characters can lead to signature mismatches. Ensure that the payload you verify is the exact match of the payload sent by SHOPLINE, including how special characters are treated
  5. Continuous Testing
    Regularly test your JSON payloads, especially after library updates or changes in your development environment, to ensure that special characters are handled consistently
# Encoded URI related
\\u003c -> <
\\u003e -> >
\\u0026 -> &

strong typed programming languages

If you are using strong typed programming languages as your backend, like GoLang, Java, etc. You might need to be aware of how your parse the payload we send you. For example, consider we send the following payload to you.

{
 "quantity": 10 
}

and in your backend, you might possibly parse this payload into

{
 "quantity: 10.0 
}

If this is the case, you will generate a different signature and might fail when you compare the one that we send and the one that you have just generated.

📘

More issues

If you have found issues in this part, please report to us and we will keep updating this part. cheers :)


Example

const secret = "b5138dd0a7c04f674260e1d3b3a762347421396fc5fc1bee55a2c2653c4207bd" // dun worry, this secret is not for production use
const payload = {
    "event": "Application",
    "merchant_id": "5dad5d2604515400018dcc90",
    "resource": {
        "_id": "607fd9c2ff790b001cd23353",
        "merchant_id": "5dad5d2604515400018dcc90",
        "updated_at": "2021-04-21T08:36:17.892Z"
    },
    "topic": "application/uninstall"
}
const timestamp = "1618994178"

const sign = YourGenerationFunction(secret, payload, timestamp) // follow the steps above
sign == "ae8b68f6a26d8f95290c761d10dbce01c775fd4d734e942e643aee20c86ebf4b" // true