Webhooks are critical for real-time notifications within our Layer1 platform, enabling automated updates related to payments, conversions, or new address creation events. When such events occur, Layer1 makes an HTTP request to the URL configured by the user for the webhooks.

Acknowledging Webhooks

The Layer1 webhooks service expects to receive a 200 HTTP status code as an acknowledgment of a successfully received and validated webhook. This response should be sent immediately upon receipt of the webhook to confirm its delivery and avoid timeout errors.

Webhook Validation

Webhooks include an x-signature header, which enables servers to authenticate the request using a secret key specific to your server setup.

Signature Calculation with ECDSA

The process to calculate and verify the signature using ECDSA

🚧

Capture the raw payload: Do not parse the incoming HTTP request into an object or re-serialize it. Instead, capture the raw payload as a string to ensure that the signature verification process is accurate.

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
import base64

# Replace this with your webhook public key
public_key_base64 = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAExn8LhKa3YnVvGHeyT+siyu9+B5knDRtigP4R08nw7Fp0lbXtwoiAO1N0LOj7k39JY5iM385BJrRV2u5Y4N0Qxg=="

# This comes from X-Signature header
signature_base64 = "MEYCIQCtvKgMTivqsT3S2G3qD46lK0+FD7ECW4dK2MtaivfWvwIhALJly6ZqemabK+gYGNWpZACzj1ApJ6immVuIQ0MxONXV"

# This is the body of the request sent to you
message = b"hello world"

# Load the public key from the base64-encoded string
public_key_loaded = serialization.load_der_public_key(
    base64.b64decode(public_key_base64)
)

# Verify the signature using the loaded public key
try:
    public_key_loaded.verify(
        base64.b64decode(signature_base64),
        message,
        ec.ECDSA(hashes.SHA256())
    )
    print("\nSignature verification succeeded!")
except InvalidSignature:
    print("\nSignature verification failed!")

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;

public class JavaSignatureExample {
  public static void main(String[] args) throws Exception {
    // If you don't already have BouncyCastle as a provider, add it
    if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
      Security.addProvider(new BouncyCastleProvider());
    }

    // Public key provided from webhook configuration
    String publicKeyBase64 = "MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAExn8LhKa3YnVvGHeyT+siyu9+B5knDRtigP4R08nw7Fp0lbXtwoiAO1N0LOj7k39JY5iM385BJrRV2u5Y4N0Qxg==";

    // From X-Signature header
    String signatureBase64 = "MEYCIQCtvKgMTivqsT3S2G3qD46lK0+FD7ECW4dK2MtaivfWvwIhALJly6ZqemabK+gYGNWpZACzj1ApJ6immVuIQ0MxONXV";

    // Body of the request
    String body = "hello world";

    KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC");
    X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicKeyBase64));


    PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

    Signature signature = Signature.getInstance("SHA256withECDSA");
    signature.initVerify(publicKey);

    // The body of the request
    signature.update(body.getBytes());

    boolean verified = signature.verify(Base64.decode(signatureBase64));

    System.out.println("Signature is valid: " + verified);
  }
}