Validating Webhook Notification

To validate that the webhook notifications are coming from Wello, you will need another public key from us to check. We will send and sign Webhook events to your endpoint and include the signature in the headers to you. We will have the same list of headers as those required for REST API requests, and use the same signature method to set the value for x-api-signature (Check REST API authentication)

Version 1.1

If you set your webhook version as "version": "1.1", we will generate the HMAC by hashing the contents of the webhook notification using the signature key you provided in your webhook update action, and then send it in the hex-encoded (Base16).


import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"net/http"
)

func webhookHandler(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "failed to read body", http.StatusInternalServerError)
		return
	}
	defer r.Body.Close()

	r.Body = io.NopCloser(io.LimitReader(io.NopCloser(bytes.NewReader(body)), int64(len(body))))

	key := []byte("WswEFtz4zs83tUQ4$EVc0UNX2G1?sgtn")

	mac := hmac.New(sha256.New, key)
	mac.Write(body)
	signatureCalc := hex.EncodeToString(mac.Sum(nil))
	signature := r.Header.Get("x-api-signature")

	if signature == signatureCalc {
		// process the body
	} else {
		// illegal request
	}
}
async function webhookHandler(req: IncomingMessage, res: ServerResponse) {

  const body = await new Promise<string>((resolve, reject) => {
    let data = '';
    req.on('data', chunk => data += chunk);
    req.on('end', () => resolve(data));
    req.on('error', err => reject(err));
  });
  const signature = req.headers['x-api-signature'];
  const key = 'WswEFtz4zs83tUQ4$EVc0UNX2G1?sgtn';  // your signature key
  const mac = crypto.createHmac('sha256', key);
  mac.update(body);
  const signatureCalc = mac.digest('hex');
  if (signature == signatureCalc) {
    // process the content
  } else {
  	// invalid request
	}
}

Legacy

We will convert the data from Webhooks (which is the order details) into the key-value pairs along with the headers except x-api-signature, and join the elements by using “&”. Then we will use the secret key generated on our side to sign the payload and put the value in the x-api-signature.

The public key we provide to you by using SHA256withRSA can be used to decrypt the value of x-api-signature, and check if the Webhook data is matched with values decrypted. The public key value will also be passed in the x-api-clientid header. However, we would recommend you to use the one we told you instead of header value for better security, and we won’t frequently change the public key for webhook message validation.

Let’s use order success updates as an example:

{
  data: 
  {
        "welloPreOrderId": "4220317678178361088",
        "merchantOrderId": "1221212121212",
        "side": "BUY",
        "cryptoCurrency": "ETH",
        "fiatCurrency": "EUR",
        "requestedCurrency": "EUR",
        "requestedAmount": "1000",
        "orderStatus": "SUCCESS",
        "quotePrice": "4.40505409",
        "fiatAmount": "1000",
        "cryptoAmount": "1.1393",
        "fees": {
            "tradeFee": "99",
            "merchantFee": 0,
            "networkFee": "0",
            "total": "109"
        },
        "payment": {
            "method": "SEPA",
            "iban": ""
        },
        "crypto": {
            "network": "ETH",
            "address": "0x71C7656EC7ab88b098defB751B7401B5f6d8976F",
            "txId": null
        },
        "failCode": "",
        "failReason": "",
        "createdAt": "1729508936145",
        "updatedAt": "1722411925095"
    }
}

Where you can retrieve the headers information from POST method as below:

{
  method: “POST”,
  headers:
  {
    x-api-clientid: <Wello Public Key to validate x-api-signature value>
    x-api-nonce: <32-character random and unique nonce string>
    x-api-timestamp: <timestamp string sent>
    x-api-signature: <signed payload (data + x-api-clientid + x-api-nonce + x-api-timestamp)>
}
}

You can use the following sample code for the signature verification

Signature signature = Signature.getInstance("SHA256withRSA");
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
PublicKey key = KeyFactory.getInstance("RSA").generatePublic(keySpec);
signature.initVerify(key);
signature.update(body.getBytes(StandardCharsets.UTF_8));
boolean verified = signature.verify(Base64.getDecoder().decode(signatureStr));

By decrypting the x-api-signature value and comparing it with the webhook’s data and other headers’ values, you can check if it is coming from Wello.