Validate Square Subscription Webhook Request using Postman

Last modified date

Square Logo

We may earn commissions from the companies mentioned in this post. View our FTC disclosure for more information.

Verifying event notifications can be a bit of a headache sometimes. I took a dive into setting up Square subscription plans for a business of mine and learned the hard way. PHP’s hash_hmac() function has a parameter that, when set, will cause the function to return binary data. Javascript has no equivalent function for returning the same result, and since the Square validation relies on two signatures matching, it’s not possible to create the signature in Postman alone. It’s for that reason that I created this Postman Pre-request script. I’m sharing my work with you today so you can have a launchpad for testing your own webhook endpoint! I hope it’s clear, and that you find it useful. Please let me know in the comments. Also, let me know if you have questions.

Requirements:

  • A registered webhook notification URL
  • The signature key generated by registering the webhook notification URL
  • Some code at the end of the webhook notification URL to validate the request and do the stuff you need to have done when it receives a message
  • Merchant id, customer id, location id, plan id
  • A custom (and separate) endpoint used to generate a signature. It should accept POST ‘message’ and POST ‘key’. The key is your signature key. The message is the webhook notification url + request body. The Pre-request script below takes care of posting the data to this custom endpoint.

Please review the Square documentation on validating requests first. If you like, you can then skip below the code examples for the “run in Postman” button. That allows you to fork my repo, copy the resources into your own, or simply load up the public collection so you can take a look. You need a request body. Leave the PLACEHOLDER text alone. The Pre-request script will handle replacing those values. Feel free to change an of the other values. Set the request body type to raw, and add a Content-Type header of application/json.

Request body

{
  "merchant_id": "PLACEHOLDER",
  "type": "subscription.created",
  "event_id": "PLACEHOLDER",
  "created_at": "PLACEHOLDER",
  "data": {
    "type": "subscription",
    "id": "PLACEHOLDER",
    "object": {
      "subscription": {
        "created_date": "2020-07-15",
        "customer_id": "PLACEHOLDER",
        "id": "PLACEHOLDER",
        "location_id": "PLACEHOLDER",
        "plan_id": "PLACEHOLDER",
        "start_date": "2020-07-15",
        "status": "ACTIVE",
        "tax_percentage": "5",
        "timezone": "America/Chicago",
        "version": 1594790050754
      }
    }
  }
}

Pre-request script

/*
* This Pre-request script will format the body of the post request
* with the necessary values to test your webhook. Nothing here needs
* editing. Just add the environment variables below to your environment.
* 
* Reference: https://developer.squareup.com/docs/webhooks/step3validate
* 
* Environment variables required:
* 
* HASH_ENDPOINT         - Endpoint used to return a hash for signature comparison
* WEBHOOK_ENDPOINT_URL  - Webhook notification URL registered with Square
* MERCHANT_ID           - Your merchant id
* CUSTOMER_ID           - Valid customer id
* LOCATION_ID           - Business location id
* PLAN_ID               - Subscription plan id
* SIGNATURE_KEY         - Webhook notification URL signature key
*/

var uuid = require('uuid'),
    id = uuid.v4(),
    date = new Date(),
    jsonData = JSON.parse(pm.request.body.raw),
    body = "";

jsonData.merchant_id = pm.environment.get("MERCHANT_ID");
jsonData.event_id = uuid.v4();
jsonData.created_at = date.toISOString();
jsonData.data.id = id;
jsonData.data.object.subscription.customer_id = pm.environment.get("CUSTOMER_ID");
jsonData.data.object.subscription.id = id;
jsonData.data.object.subscription.location_id = pm.environment.get("LOCATION_ID");
jsonData.data.object.subscription.plan_id = pm.environment.get("PLAN_ID");

body = JSON.stringify(jsonData);

pm.sendRequest({
  url: pm.variables.get("HASH_ENDPOINT"),
  method: 'POST',
  body: {
    mode: 'urlencoded',
    urlencoded : [
      { key: 'message', value: pm.variables.get("WEBHOOK_ENDPOINT_URL") + body },
      { key: 'key', value: pm.variables.get("SIGNATURE_KEY") }
    ]
  }
}, (error, response) => {
    /*
    * Your server side function should return json with hash as the key.
    * It may also be wise to do a string-to-lowercase on the X-Square-HmacSha256-Signature
    * header name when you check for it. The case is inconsistent in some online examples.
    */
    var json = response.json();
    pm.request.headers.add(`X-Square-HmacSha256-Signature: ${json.hash}`);
    pm.request.body.raw = body;
});

The Pre-request script is the most important part of this puzzle. It will fill in the placeholder values with random data and make a request to your custom endpoint (HASH_ENDPOINT) to get a valid signature. It will then pass the signature as a proper formatted header to your WEBHOOK_ENDPOINT_URL for validation. My backend is PHP, so here’s example code similar to what I use to create the hash:

<?php

class someAwesomeClass
{
    private function hash()
    {
        $hash = hash_hmac(
            "sha256",
            rawurldecode($_POST['message']),
            rawurldecode($_POST['key']),
            true
        );

        die(json_encode(['hash' => base64_encode($hash)]));
    }
}

The code provided on the Square developer site is what I’m using for request validation.

<?php
// The URL where event notifications are sent.
define("NOTIFICATION_URL", "https://example.com/webhook");

// The signature key defined for the subscription.
define("SIGNATURE_KEY", "asdf1234");

// isFromSquare generates a signature from the url and body and compares it to the Square signature header.
function isFromSquare($signature, $body) {
  $hash = hash_hmac("sha256", NOTIFICATION_URL.$body, SIGNATURE_KEY, true);
  return  base64_encode($hash) == $signature;
}

// Start a simple server for local testing.
// Different frameworks may provide the raw request body in other ways.
// INSTRUCTIONS
// 1. Run the server:
//    php -S localhost:8000 server.php
// 2. Send the following request from a separate terminal:
//    curl -vX POST localhost:8000 -d '{"hello":"world"}' -H "X-Square-HmacSha256-Signature: 2kRE5qRU2tR+tBGlDwMEw2avJ7QM4ikPYD/PJ3bd9Og="

$headers = apache_request_headers();
$signature = $headers["X-Square-HmacSha256-Signature"];

$body = '';   
$handle = fopen('php://input', 'r');
while(!feof($handle)) {
    $body .= fread($handle, 1024);
}

if (isFromSquare($signature, $body)) {
  // Signature is valid. Return 200 OK.
  http_response_code(200);
  echo "Request body: $body\n";
} else {
  // Signature is invalid. Return 403 Forbidden.
  http_response_code(403);
}
return http_response_code();
?>

Good luck, have fun, and make money! Let me know if you have any questions. I can (probably) answer questions about javascript and PHP. If you’re backend is something different, I can do my best to help!

The MAN Himself

Author of Modern Guitar Method. Also, please listen to my new album. I think it's the best jazz album of 2021 and It's available everywhere!

Share