Learn about the third step for setting up Square webhooks, which is to verify that you receive the event notification and validate that the notification originated from Square.
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.
To verify your event notification subscription using webhook.site:
- Go to webhook.site in a browser, copy the provided unique URL to the clipboard, and leave the page open.
- Create a webhook subscription by following the steps in Subscribe to Event Notifications. For testing purposes, choose Select All under Events.
- Enter the unique URL you copied from webhook.site as your notification URL.
- Trigger an event from API Explorer. For example, generate a
customer.createdevent by calling theCreateCustomerendpoint in the Customers API and providing a first or last name, company name, email address, or phone number. - Return to the webhook.site page to view the event notification.
After you verify that your webhook subscription is working, you need to add code to your notification URL so that your application can process the event.
Your notification URL is public and can be called by anyone, so you must validate each event notification to confirm that it originated from Square. A non-Square post can potentially compromise your application. Requests that fail validation cannot be trusted and should be discarded.
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:
- The signature key for your webhook subscription.
- The notification URL for your webhook subscription.
- The raw body of the request.
To validate the webhook notification, generate the HMAC-SHA-256 signature in your own code and compare it to the x-square-hmacsha256-signature of the event notification you received. If needed, you can find your signature key and notification URL for your application's webhook subscription under Webhooks in the Developer Console.
Important
A timing attack can exploit a signature comparison that stops at the first difference. If you implement signature validation yourself, use a constant-time comparison. The SDK helper examples below verify the signature, but constant-time comparison support varies by SDK.
The following examples show how to use the WebhooksHelper utility in the Square SDKs to verify an event notification using its raw request body, signature header, signature key, and notification URL.
The following examples use the Node.js SDK. For installation instructions, see Square Node.js SDK Quickstart.
// server.mjs import * as http from 'http'; import { WebhooksHelper } from "square"; // 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'; // Verify the raw request body and Square signature header. async function isFromSquare(signature, body) { return await WebhooksHelper.verifySignature({ requestBody: body, signatureHeader: signature, signatureKey: SIGNATURE_KEY, notificationUrl: NOTIFICATION_URL }); } async function requestHandler(request, response) { let body = ''; request.setEncoding('utf8'); request.on('data', function(chunk) { body += chunk; }); request.on('end', async function() { const signature = request.headers['x-square-hmacsha256-signature']; if (await isFromSquare(signature, body)) { // Signature is valid. Return 200 OK. console.info("Request body: " + body); response.writeHead(200); } else { // Signature is invalid. Return 403 Forbidden. console.info("Invalid signature"); response.writeHead(403); } response.end(); }); } // Start a simple server for local testing. // Different frameworks may provide the raw request body in other ways. // INSTRUCTIONS // After installing the SDK: // 1. Run the server: // node server.mjs // 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);
The following example uses the Python SDK. For installation instructions, see Square Python SDK Quickstart.
# server.py from http.server import BaseHTTPRequestHandler, HTTPServer from square.utils.webhooks_helper import verify_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') or '' # Verify the raw request body and Square signature header. is_from_square = verify_signature( request_body=body, signature_header=square_signature, signature_key=SIGNATURE_KEY, notification_url=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 # After installing the SDK: # 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()
The following example uses the .NET SDK. For installation instructions, see Square .NET SDK Quickstart.
// Program.cs using Square; 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> /// Verify the raw request body and Square signature header. /// </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.VerifySignature(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. Create the project and install the Square SDK: /// <code>dotnet new console --name webhook-example</code> /// <code>cd webhook-example</code> /// <code>dotnet add package Square</code> /// 2. Replace Program.cs with this example, and then run the server: /// <code>dotnet run</code> /// 3. 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; } } } } }
The following example uses the Go SDK. For installation instructions, see Square Go SDK Quickstart.
// server.go package main import ( "context" "fmt" "io" "net/http" "os" square "github.com/square/square-go-sdk/v5" squareclient "github.com/square/square-go-sdk/v5/client" ) // The URL where event notifications are sent. // The signature key defined for the subscription. const ( NOTIFICATION_URL = "https://example.com/webhook" SIGNATURE_KEY = "asdf1234" ) // Verify the raw request body and Square signature header. func isFromSquare(signature string, body []byte) bool { if len(body) == 0 { return false } client := squareclient.NewClient() err := client.Webhooks.VerifySignature( context.TODO(), &square.VerifySignatureRequest{ RequestBody: string(body), SignatureHeader: signature, SignatureKey: SIGNATURE_KEY, NotificationURL: NOTIFICATION_URL, }, ) return err == nil } func requestHandler(w http.ResponseWriter, r *http.Request) { signature := r.Header.Get("X-Square-HmacSha256-Signature") body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } if isFromSquare(signature, body) { // Signature is valid. Return 200 OK. w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Request body: %s\n", body) } else { // Signature is invalid. Return 403 Forbidden. w.WriteHeader(http.StatusForbidden) fmt.Fprintln(w, "Invalid signature") } } // Start a simple server for local testing. // Different frameworks may provide the raw request body in other ways. // INSTRUCTIONS // After initializing a new Go module: // 1. Install the SDK: // go get github.com/square/square-go-sdk/v5 // 2. Run the server: // go run server.go // 3. 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("/", requestHandler) port := "8000" fmt.Printf("Server is listening on port %s\n", port) if err := http.ListenAndServe(":"+port, nil); err != nil { fmt.Fprintf(os.Stderr, "Failed to start server: %v\n", err) os.Exit(1) } }
The following example uses the Java SDK. For instructions to run this example, see Setup steps for Java.
// Server.java package com.square.examples; 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"; private static byte[] readAllBytes(java.io.InputStream input) throws java.io.IOException { java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = input.read(buffer)) != -1) { output.write(buffer, 0, bytesRead); } return output.toByteArray(); } // Start a simple server for local testing. // Different frameworks may provide the raw request body in other ways. // INSTRUCTIONS // After installing the SDK: // 1. Run the server: // See the setup steps below. // 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 -> { java.util.List<String> sigHeaders = httpExchange.getRequestHeaders().get("x-square-hmacsha256-signature"); String requestBody = new String(readAllBytes(httpExchange.getRequestBody()), java.nio.charset.StandardCharsets.UTF_8); boolean isFromSquare = false; if (sigHeaders != null && sigHeaders.size() == 1) { String signature = sigHeaders.get(0); // Verify the raw request body and Square signature header. isFromSquare = WebhooksHelper.verifySignature(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); Logger.getLogger(Server.class.getName()).log(Level.INFO, "Invalid signature"); } httpExchange.getResponseBody().close(); }); server.start(); } catch (Throwable tr) { tr.printStackTrace(); } } }
This exercise requires Oracle Java SE Development Kit (Java version 8 or later) and Apache Maven for dependency management.
-
Create a directory for your project and create the
pom.xmlconfiguration file.mkdir webhook-example && cd webhook-example && touch pom.xml -
Paste the following content into
pom.xml, and then replace SDK_VERSION_HERE with the target SDK version. For example:<version>48.0.0.20260916</version><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.square.examples</groupId> <artifactId>webhook-example</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.squareup</groupId> <artifactId>square</artifactId> <version>SDK_VERSION_HERE</version> </dependency> </dependencies> </project> -
Create the
Server.javafile in the package directory.mkdir -p src/main/java/com/square/examples && touch src/main/java/com/square/examples/Server.java -
Paste the example server code into
Server.java. -
Run the following commands from the
webhook-exampledirectory:-
Download project dependencies.
mvn dependency:copy-dependencies -
Start the server.
javac -cp "target/dependency/*" src/main/java/com/square/examples/Server.java && java -cp "target/dependency/*:src/main/java" com.square.examples.Server
-
-
After the server starts, send the test cURL command from a separate terminal.
curl -vX POST localhost:8000 -d '{"hello":"world"}' -H "X-Square-HmacSha256-Signature: 2kRE5qRU2tR+tBGlDwMEw2avJ7QM4ikPYD/PJ3bd9Og="
The following examples use the PHP SDK. For installation instructions, see Square PHP SDK Quickstart.
<?php // server.php require 'vendor/autoload.php'; use Square\Utils\WebhooksHelper; // 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"); // Start a simple server for local testing. // Different frameworks may provide the raw request body in other ways. // INSTRUCTIONS // After installing the SDK: // 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); } // Verify the raw request body and Square signature header. if (WebhooksHelper::verifySignature($body, $signature, SIGNATURE_KEY, NOTIFICATION_URL)) { // 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); echo "Invalid signature\n"; } return http_response_code(); ?>
The following example uses the legacy webhook helper bundled with the Ruby SDK. For installation instructions, see Square Ruby SDK Quickstart.
# server.rb require 'base64' require 'openssl' require 'sinatra' require 'square_legacy' # The URL where event notifications are sent. NOTIFICATION_URL = "https://example.com/webhook" # The signature key defined for the subscription. SIGNATURE_KEY = "asdf1234" # Verify the raw request body and Square signature header. def is_from_square(signature, body) return SquareLegacy::WebhooksHelper.is_valid_webhook_event_signature(body, signature, SIGNATURE_KEY, NOTIFICATION_URL) end # Start a simple server for local testing. # Different frameworks may provide the raw request body in other ways. # INSTRUCTIONS # After installing the SDK: # 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 is invalid. Return 403 Forbidden. status 403 end end