Webhook Signature Mismatch for oauth.authorization.revoked Event

Subject: Webhook Signature Mismatch for oauth.authorization.revoked Event

Message:

Hi Square Team,

I’m experiencing an issue with verifying the webhook signature for the oauth.authorization.revoked event. Despite following the documentation and ensuring all steps are correct, the computed HMAC-SHA256 signature does not match the x-square-signature header sent by Square.

Details

  1. Webhook Endpoint:
    Example: https://my-webhook-endpoint.com/square-webhook (using HTTPS, with no known modifications to the payload).

  2. Payload (logged in raw format):
    Here is the exact raw payload received from Square:

{
  "merchant_id": "MLPQCNE4FSEY6",
  "type": "oauth.authorization.revoked",
  "event_id": "1a01f5d6-0ded-31f3-94ff-a49e3c180676",
  "created_at": "2024-12-25T01:57:41.595704723Z",
  "data": {
    "type": "revocation",
    "id": "c7a28d0a-b92f-4ee8-bcd6-e5800c544b10",
    "object": {
      "revocation": {
        "revoked_at": "2024-12-25T01:57:41Z",
        "revoker_type": "APPLICATION"
      }
    }
  }
}
  1. Steps Taken to Compute the Signature:
    Webhook Signature Key: [REDACTED FOR SECURITY]
    Computation Method:
import hmac
import hashlib
import base64

# Signature Key
webhook_signature_key = "[REDACTED FOR SECURITY]"

# Compute HMAC-SHA256
computed_hash = hmac.new(
    webhook_signature_key.encode("utf-8"),
    raw_payload,  # Received raw payload (as bytes)
    hashlib.sha256
).digest()

# Base64 encode the result
expected_signature = base64.b64encode(computed_hash).decode("utf-8")
print(f"Computed Signature: {expected_signature}")
  1. Observed vs. Expected Signatures:
    Computed Signature: tJvEZm2mnT9GairDCIJDBAA06q+58TNGb60VBoO9epw=
    x-square-signature Header: n3S9Zm9KqdoONGkK+BavaFnweqE=

These do not match.

  1. Additional Tests:
  • Verified the raw payload using xxd to ensure no newline or hidden characters.
  • Confirmed the correct webhook signature key is being used.
  • Attempted URL concatenation (as in older Square examples) but found it unnecessary for V2 webhooks.

Request

Could you please confirm:

  1. Whether there are additional steps or transformations applied by Square when generating the HMAC signature?
  2. If the payload or signature computation might differ for the oauth.authorization.revoked event specifically?
  3. Any known causes or resolutions for such mismatches?

I can provide further details privately if needed. Thank you for your assistance!

:wave: I’m Square Dev AI, here to help answer your question!
Based on the information you’ve provided, the following documentation will help answer your question:

https://developer.squareup.com/docs/app-marketplace/requirements/oauth-api

Additional Documentation

OAuth API
OAuth API
Payments API Webhooks

If this documentation helped you find a solution, please check the box indicating that this reply solves the problem.
Otherwise one of our Developer Advocates will provide additional assistance shortly.

Never mind - I figured out the solution. Just followed the python code example from the following page and it worked!

Here is the code example:

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()

The issue you’re experiencing might be due to the encoding of the webhook signature key. The key provided by Square is base64-encoded, so you need to decode it before using it to compute the HMAC.

Here’s how you can modify your Python code:

import hmac
import hashlib
import base64

# Signature Key
webhook_signature_key = "[REDACTED FOR SECURITY]"

# Decode the Signature Key
decoded_key = base64.b64decode(webhook_signature_key)

# Compute HMAC-SHA256
computed_hash = hmac.new(
    decoded_key,
    raw_payload,  # Received raw payload (as bytes)
    hashlib.sha256
).digest()

# Base64 encode the result
expected_signature = base64.b64encode(computed_hash).decode("utf-8")
print(f"Computed Signature: {expected_signature}")

This should give you the correct computed signature that matches the x-square-signature header sent by Square. If you’re still experiencing issues, please let us know.

This answer was reviewed by @Bryan-Square.

Glad to hear that you got it to work. :slightly_smiling_face: