The third step to create Square webhooks is to have your application verify the event notification and validate the data included.
Webhooks

Step 3: Verify and Validate an Event Notification

You can verify the creation and receipt of an event notification using either a test endpoint you create or a public site such as webhook.site. You can use API Explorer to generate events that webhooks can subscribe to. For more information about using API Explorer, see Testing with API Explorer.

To verify your event notification subscription using webhook.site:

  1. Go to webhook.site in a browser, copy the provided unique URL to the clipboard, and leave the page open.

  2. Create a webhook using Step 2: Subscribe to Event Notifications.

  3. Use the unique URL you copied from webhook.site for your notification URL, and then choose Select All under Events.

  4. Create an event in API Explorer. Creating a customer using the Customers API is a simple way to create an event because it requires only one of the five identity values, such as family name (last name), a given name (first name), or an email address. This generates the customer.created event.

  5. Return to the webhook.site page to view the event notification.

After you verify that your event notification subscription is working, you need to add code to your notification URL so that your application can process the event information. Because your notification URL is public and can be called by anyone, you must validate each event notification to confirm that it came from Square. A non-Square post can potentially compromise your application. All webhook notifications from Square include an x-square-hmacsha256-signature header. The value of this header is an HMAC-SHA-256 signature generated using your webhook signature key, the notification URL, and the raw body of the request. You can validate the webhook notification by generating the HMAC-SHA-256 value in your own code and comparing it to the signature of the event notification you received.

Important

A malicious agent can compromise your notification endpoint by using a timing analysis attack to determine the key you are using to decrypt and compare webhook signatures. You should use a constant-time crypto library to prevent such attacks by masking the actual time taken to decrypt and compare signatures.

The following functions generates an HMAC-SHA256 signature from your signature key, the notification URL, and the event notification body. You can then compare the result with the event notification's x-square-hmacsha256-signature.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
const { Console } = require('console');
const crypto = require('crypto');
const http = require('http');

// The URL where event notifications are sent.
const NOTIFICATION_URL = 'https://example.com/webhook';

// The signature key defined for the subscription.
const SIGNATURE_KEY = 'asdf1234';

// isFromSquare generates a signature from the url and body and compares it to the Square signature header.
function isFromSquare(signature, body) {
  const hmac = crypto.createHmac('sha256', SIGNATURE_KEY);
  hmac.update(NOTIFICATION_URL + body);
  const hash = hmac.digest('base64');

  return hash === signature;
}

function requestHandler(request, response) {
  let body = '';
  request.setEncoding('utf8');

  request.on('data', function(chunk) {
    body += chunk;
  });

  request.on('end', function() {
    const signature = request.headers['x-square-hmacsha256-signature'];
    if (isFromSquare(signature, body)) {
      // Signature is valid. Return 200 OK.
      response.writeHead(200);
      console.info("Request body: " + body);
    } else {
      // Signature is invalid. Return 403 Forbidden.
      response.writeHead(403);
    }
    response.end();
  });
}

// Start a simple server for local testing.
// Different frameworks may provide the raw request body in other ways.
// INSTRUCTIONS
// 1. Run the server:
//    node server.js
// 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="
const server = http.createServer(requestHandler);
server.listen(8000);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
from http.server import BaseHTTPRequestHandler, HTTPServer
from square.utilities.webhooks_helper import is_valid_webhook_event_signature

# The URL where event notifications are sent.
NOTIFICATION_URL = 'https://example.com/webhook'

# The signature key defined for the subscription.
SIGNATURE_KEY = 'asdf1234'

class MainHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers.get('content-length', 0))
        body = self.rfile.read(length).decode('utf-8')
        square_signature = self.headers.get('x-square-hmacsha256-signature')

        is_from_square = is_valid_webhook_event_signature(body,
                                                          square_signature,
                                                          SIGNATURE_KEY,
                                                          NOTIFICATION_URL)

        if is_from_square:
            # Signature is valid. Return 200 OK.
            self.send_response(200)
            print("Request body: {}".format(body))
        else:
            # Signature is invalid. Return 403 Forbidden.
            self.send_response(403)

        self.end_headers()

# Start a simple server for local testing.
# Different frameworks may provide the raw request body in other ways.
# INSTRUCTIONS
# 1. Run the server:
#    python server.py
# 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="
server = HTTPServer(("0.0.0.0", 8000), MainHandler)
server.serve_forever()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
using Square.Utilities;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;

public class Server
{
    /// <summary>The URL where event notifications are sent.</summary>
    private const string NOTIFICATION_URL = "https://example.com/webhook";

    /// <summary>The signature key defined for the subscription.</summary>
    private const string SIGNATURE_KEY = "asdf1234";

    /// <summary>
    /// Validate that a webhook event notification came from Square. Requests that fail validation
    /// should be discarded as they cannot be trusted.
    /// </summary>
    private static async Task<bool> IsFromSquare(HttpListenerRequest request)
    {
        using (var reader = new StreamReader(request.InputStream, Encoding.UTF8))
        {
            var signature = request.Headers.Get("x-square-hmacsha256-signature") ?? "";
            var requestBody = await reader.ReadToEndAsync();
            return WebhooksHelper.IsValidWebhookEventSignature(requestBody, signature, SIGNATURE_KEY, NOTIFICATION_URL);
        }
    }

    /// <summary>
    /// Start a simple server for local testing. Different frameworks may provide the raw request body in other ways.
    /// </summary>
    /// <remarks>
    //  INSTRUCTIONS
    //  1. Run the server:
    //     (You will first need to include your own csharp.csproj file.)
    //     <code>dotnet run</code>
    //  2. Send the following request from a separate terminal:
    //     <code>curl -vX POST localhost:8000 -d '{"hello":"world"}' -H "X-Square-HmacSha256-Signature: 2kRE5qRU2tR+tBGlDwMEw2avJ7QM4ikPYD/PJ3bd9Og="</code>
    /// </remarks>
    public static void Main(string[] args)
    {
        HttpListener server = new HttpListener();
        server.Prefixes.Add("http://localhost:8000/");
        server.Start();

        while (true)
        {
            HttpListenerContext context = server.GetContext();

            var task = IsFromSquare(context.Request);
            task.Wait();
            bool isFromSquare = task.Result;

            using (HttpListenerResponse response = context.Response)
            {
                if (isFromSquare)
                {
                    // Signature is valid. Return 200 OK.
                    response.StatusCode = 200;
                }
                else
                {
                    // Signature is invalid. Return 403 Forbidden.
                    response.StatusCode = 403;
                }
            }
         }
      }
   }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
package main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
    "encoding/json"
    "io/ioutil"
    "log"
    "net/http"
)

const (
    // The URL where event notifications are sent.
    NOTIFICATION_URL = "https://example.com/webhook"

    // The signature key defined for the subscription.
    SIGNATURE_KEY = "asdf1234"
)

// isFromSquare generates a signature from the url and body and compares it to the Square signature header.
func isFromSquare(signature string, body []byte) bool {
    payload := new(bytes.Buffer)
    _ = json.Compact(payload, body)

    appended := append([]byte(NOTIFICATION_URL), payload.Bytes()...)
    key := []byte(SIGNATURE_KEY)
    hash := hmac.New(sha256.New, key)
    hash.Write(appended)

    return signature == base64.StdEncoding.EncodeToString(hash.Sum(nil))

}

// Start a simple server for local testing.
// Different frameworks may provide the raw request body in other ways.
// INSTRUCTIONS
// 1. Run the server:
//    go run server.go
// 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="
func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        defer r.Body.Close()
        signature := r.Header.Get("x-square-hmacsha256-signature")
        body, _ := ioutil.ReadAll(r.Body)

        if isFromSquare(signature, body) {
            // Signature is valid. Return 200 OK.
            w.WriteHeader(200)
            log.Printf("Request body: %v\n", string(body))
        } else {
            // Signature is invalid. Return 403 Forbidden.
            w.WriteHeader(403)
        }
    })
    log.Fatal(http.ListenAndServe(":8000", nil))
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
import com.squareup.square.utilities.WebhooksHelper;
import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Server {
  // The URL where event notifications are sent.
  private static final String NOTIFICATION_URL = "https://example.com/webhook";

  // The signature key defined for the subscription.
  private static final String SIGNATURE_KEY = "asdf1234";

  // Start a simple server for local testing.
  // Different frameworks may provide the raw request body in other ways.
  // INSTRUCTIONS
  // 1. Run the server:
  //    javac Server.java && java Server
  // 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="
  public static void main(String[] args) {
    try {
      HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);

      server.createContext("/", httpExchange -> {
        var sigHeaders = httpExchange.getRequestHeaders().get("x-square-hmacsha256-signature");
        var requestBody = new String(httpExchange.getRequestBody().readAllBytes());
        boolean isFromSquare = false;
        if (sigHeaders.size() == 1) {
          String signature = sigHeaders.get(0);
          isFromSquare = WebhooksHelper.isValidWebhookEventSignature(requestBody, signature, SIGNATURE_KEY, NOTIFICATION_URL);
        }

        if (isFromSquare) {
          // Signature is valid. Return 200 OK.
          httpExchange.sendResponseHeaders(200, 0);
          Logger.getLogger(Server.class.getName()).log(Level.INFO, "Request body: " + requestBody);
        } else {
          // Signature is invalid. Return 403 Forbidden.
          httpExchange.sendResponseHeaders(403, 0);
        }
        httpExchange.getResponseBody().close();
      });

      server.start();
    } catch (Throwable tr) {
      tr.printStackTrace();
    }
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
<?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();
?>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
require 'base64'
require 'openssl'
require 'sinatra'

# The URL where event notifications are sent.
NOTIFICATION_URL = "https://example.com/webhook".freeze

# The signature key defined for the subscription.
SIGNATURE_KEY = "asdf1234".freeze

# isFromSquare generates a signature from the url and body and compares it to the Square signature header.
def is_from_square(signature, body)
  data = NOTIFICATION_URL + body

  digest = OpenSSL::Digest.new('sha256')
  hmac = OpenSSL::HMAC.digest(digest, SIGNATURE_KEY, data)

  return signature == Base64.encode64(hmac).chomp!
end

# Start a simple server for local testing.
# Different frameworks may provide the raw request body in other ways.
# INSTRUCTIONS
# 1. Run the server:
#    (You may need to `gem install sinatra` first.)
#    ruby server.rb
# 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="
set :port, 8000
post '/' do
  signature = request.env['HTTP_X_SQUARE_HMACSHA256_SIGNATURE']
  body = request.body.read
  if is_from_square(signature, body)
    # Signature is valid. Return 200 OK.
    status 200
    puts "Request body: %s" % body
  else
    # Signature invalid valid. Return 403 Forbidden.
    status 403
  end
end

If you need more assistance, contact Developer Support or ask for help in the Developer Forums.