Webhooks, Static IPs, and ECDSA

Learn how to enhance the security of webhooks within the super.AI platform.

Introduction to Callbacks (Webhooks)

Callbacks, also known as webhooks, are user-defined HTTP callbacks that allow real-time notifications of events happening within a system.

Security Features

Static IP

To ensure secure communication between super.AI and customer systems, super.AI provides a static IP range that customers can trust. Customers can whitelist this IP in their firewalls to allow incoming traffic from super.AI. The IP range will be shared with customers upon request.

ECDSA request body validation

When we send a request to a customer’s system we can include an encrypted header so that the customers can validate it against the request body. The public key for the validation will be shared with customers upon request. Please contact [email protected] for pricing and activation details.

Verify Incoming Requests

To verify the signature of incoming webhook requests, follow these steps in your application code:

  1. Extract the X-SuperAI-Webhook-Signature header from the incoming request.
  2. Retrieve the raw request payload.
  3. Ensure that the JSON keys in the payload are sorted by key name to guarantee determinism in the signature process.
  4. Use the public verification key provided by superAI to verify the signature of the request using the ECDSA algorithm.

Note: If the verification fails, we recommend returning an appropriate HTTP error code (e.g., 401 (Unauthorized) or 403 (Forbidden)) to indicate the failure and allow super.AI to detect potential issues.

Python Example

Here's an example in Python using the ecdsa library:

from hashlib import sha256
import json
from ecdsa import VerifyingKey
from ecdsa.util import sigdecode_string


# This is an example ECDSA public key in PEM format which would be shared by super.ai
public_key_pem = """-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIlWHr5GwMVGdjBC9J7AUkNe3hCsP
ZrMUwSi+epK01ae7D+6L+Tj3KsgG2ZtJyXJtB1QZSoXLC7j2U+j2QC9J5Q==
-----END PUBLIC KEY-----"""


class MockRequest:
    """Mock request object that is used to simulate the request object that super.ai would send to your webhook."""

    def __init__(self):
        self.method = "POST"
        self.url = "https://api.superai.io/webhook"
        self.headers = {
            "Content-Type": "application/json",
            "X-SuperAI-Version": "1",
            "X-SuperAI-Webhook-Signature": "96f4835eaf0447c696b4367b2c2944000a10c73459af05af97e92cb3a2420b13b793e06367fed0d923799dabb3e9347918a4791bc2bf8229af425b138a1343f2",
        }
        self.body = {
            "action": "RESOLVED",
            "id": 8737458,
            "postProcessingEnabled": False,
            "state": "COMPLETED",
            "uuid": "6dccecff-25a0-4398-890f-57b252488a35",
        }


def verify_signature(request, public_key_pem):
    """Verify the signature of the request using the public key provided by super.ai."""
    try:
        signature = request.headers["X-SuperAI-Webhook-Signature"]
        decoded = bytes.fromhex(signature)

        # super.ai sends the boody sorted
        body = dict(request.body.items())
        body_json = json.dumps(body)  # Match the JSON encoding
        message = body_json.encode("utf-8")

        public_key = VerifyingKey.from_pem(public_key_pem)
        return public_key.verify(decoded, message, sha256, sigdecode=sigdecode_string)
    except Exception as e:
        print(f"Verification error: {type(e).__name__}: {str(e)}")
        return False


def main():
    request = MockRequest()
    is_valid = verify_signature(request, public_key_pem)
    
    if not is_valid:
        # Return an appropriate HTTP error code so that super.AI can detect the issue
        return jsonify({"error": "Invalid signature"}), 40
    print("Verification result:", is_valid)


if __name__ == "__main__":
    main()


Why Return an Error Code?

When super.AI sends a webhook request to your endpoint, it expects a successful HTTP status code (e.g., 2xx) if everything is correct. If your endpoint returns a 4xx (client error) or 5xx (server error) status code, super.AI knows something went wrong. By returning 401 (Unauthorized) or 403 (Forbidden) when the signature verification fails, you explicitly communicate that the request could not be validated, enabling super.AI to detect and log the issue accordingly.


What's next