Can't verify the webhook signature in sandbox

I’m having trouble to match the fireblocks header signature with the payload. I am getting false every time after the verification.

Code snippet:

const signature = req.headers['fireblocks-signature'];
            const publicKey = process.env.FIREBLOCKS_WEBHOOK_API_PUBLIC_KEY;
            const verifier = crypto.createVerify('RSA-SHA512');
            const message = JSON.stringify(req.payload);
            verifier.write(message);
            verifier.end();

            const isVerified = verifier.verify(publicKey, signature, 'base64');
            console.log('Verified:', isVerified);

Request Headers:

{
   "x-forwarded-for":"18.189.135.42,172.31.35.148",
   "x-forwarded-proto":"https,http",
   "x-forwarded-port":"443,80",
   "x-amzn-trace-id":"Root=1-6628f19e-0438edd11aa78ee218ad3b58",
   "accept":"application/json, text/plain, */*",
   "content-type":"application/json",
   "fireblocks-signature":"nRK8gT/psgK7bC2JysRdgu/abUcnfvW0H/AJNKpWUhPEKb0+1VnOpap6xturPA6JegUOj9X8ek3TwHanVcquzqGXQ+vijJLUbzDcssbOlHgbD7staxyfFZmqGFW7SLETGSYpAg+FghacHD61MG+mEekIKgOVJkodUUhKwWwVZXFXtllvl27NXg6RVAbi0n/i4OgUHGSwarTUBgJFzmf6J/55qolBR0JtH20eWEimVXkM7gXxIZXjxhKuzM23QlCPNTaS/GyZNQ39r/yQ135z0c7jkDJjc5/R7A9KtiUMB+H5/7fnst19deuqefuiJLI5OYfkYrevFPy+aSi7FbHRkg==",
   "fireblocks-api-version":"1.4.0",
   "user-agent":"axios/0.27.2",
   "x-datadog-trace-id":"8295036793575788319",
   "x-datadog-parent-id":"8295036793575788319",
   "x-datadog-sampling-priority":"2",
   "x-datadog-tags":"_dd.p.dm=-3",
   "traceparent":"00-0000000000000000731de403c7e14f1f-731de403c7e14f1f-01",
   "tracestate":"dd=t.dm:-3;s:2",
   "content-length":"1370",
   "forwarded":"proto=http;host=preprod.apigateway.wind.app;for=\"172.31.35.148:16720\"",
   "x-forwarded-host":"preprod.apigateway.wind.app",
   "host":"localhost:3050"
}

Request Payload:

{
   "type":"TRANSACTION_STATUS_UPDATED",
   "tenantId":"5aac0205-326a-42e2-8401-37c4c6d582f9",
   "timestamp":"1713959325730",
   "data":{
      "id":"12de2b59-25bf-45be-8623-7f53b22b9ccc",
      "createdAt":"1713959314853",
      "lastUpdated":"1713959315025",
      "assetId":"AMOY_POLYGON_TEST",
      "source":{
         "id":"",
         "type":"UNKNOWN",
         "name":"External",
         "subType":""
      },
      "destination":{
         "id":"5",
         "type":"VAULT_ACCOUNT",
         "name":"user#24",
         "subType":""
      },
      "amount":"0.001",
      "networkFee":"0.000031500000315",
      "netAmount":"0.001",
      "sourceAddress":"0x03ed8856459faC1e27a950dc2C95261757162CEf",
      "destinationAddress":"0xC0B6c89Ae4A037d72DA702308B6E431e13F152E1",
      "destinationAddressDescription":"",
      "destinationTag":"",
      "status":"COMPLETED",
      "txHash":"0x96c4cb805af26d42c7dd5097ea4e88d0da060e47395614f5f0e372defb5ea331",
      "subStatus":"CONFIRMED",
      "signedBy":[
         
      ],
      "createdBy":"",
      "rejectedBy":"",
      "amountUSD":0,
      "addressType":"",
      "note":"",
      "exchangeTxId":"",
      "requestedAmount":"0.001",
      "feeCurrency":"AMOY_POLYGON_TEST",
      "operation":"TRANSFER",
      "customerRefId":null,
      "numOfConfirmations":"1",
      "amountInfo":{
         "amount":"0.001",
         "requestedAmount":"0.001",
         "netAmount":"0.001",
         "amountUSD":null
      },
      "feeInfo":{
         "networkFee":"0.000031500000315",
         "gasPrice":"1.500000015"
      },
      "destinations":[
         
      ],
      "externalTxId":null,
      "blockInfo":{
         "blockHeight":"6243175",
         "blockHash":"0x4173cff12fae18946280f3f4060bc1f54b7f5ba547779d5a1394c8429934fa41"
      },
      "signedMessages":[
         
      ],
      "index":0,
      "assetType":"BASE_ASSET"
   }
}

Sandbox Public API key got from Fireblocks Documentation:

FIREBLOCKS_WEBHOOK_API_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG\njjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5\nYlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v\n4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH\nclM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd\nCGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ\ndE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd\nHgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP\nSwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1\n4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc\nIMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk\ntSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==\n-----END PUBLIC KEY-----"

Hi iamyeasin,
I am Mohammed from Fireblocks
It seems the public key is not correct.
Please use the one described in the article below.
Thanks

Hey @mnamakwala ,

I actually tried with example as well, here is the snippet for that.

const signature = req.headers['fireblocks-signature'];
            const publicKey = `-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----`.replace(/\\n/g, '\n');
            const verifier = crypto.createVerify('RSA-SHA512');
            const message = JSON.stringify(req.payload);
            verifier.write(message);
            verifier.end();

            const isVerified = verifier.verify(publicKey, signature, 'base64'); // Talk with fireblocks to resolve signature issues
            console.log('Verified:', isVerified);

Same false result. Please let me know if you I can share anything else to sort this out.

The public key is

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw+fZuC+0vDYTf8fYnCN6
71iHg98lPHBmafmqZqb+TUexn9sH6qNIBZ5SgYFxFK6dYXIuJ5uoORzihREvZVZP
8DphdeKOMUrMr6b+Cchb2qS8qz8WS7xtyLU9GnBn6M5mWfjkjQr1jbilH15Zvcpz
ECC8aPUAy2EbHpnr10if2IHkIAWLYD+0khpCjpWtsfuX+LxqzlqQVW9xc6z7tshK
eCSEa6Oh8+ia7Zlu0b+2xmy2Arb6xGl+s+Rnof4lsq9tZS6f03huc+XVTmd6H2We
WxFMfGyDCX2akEg2aAvx7231/6S0vBFGiX0C+3GbXlieHDplLGoODHUt5hxbPJnK
IwIDAQAB
-----END PUBLIC KEY-----

changed the public key ,still getting false.

Hi @mnamakwala , :smiley:

Is there any update for me? I am kind of out of ideas, not sure what’s wrong.

hey @iamyeasin,

I was not able to verify the request you sent here as well.
Having said that, I was testing the same code with the same public key on my sandbox account and just generated a new event of adding a new vault account and it worked.

Can you please check that you are able to verify the following request?

Body:

{
  "type": "VAULT_ACCOUNT_ADDED",
  "tenantId": "66821abe-c8a9-4676-b99a-929290519743",
  "timestamp": 1714199567397,
  "data": {
    "id": "3",
    "name": "Webhook Event Test",
    "hiddenOnUI": false,
    "assets": []
  }
}

fireblocks-signature:

MG+eJGzYhfjFQHDDL8pWN+KKSkGJYwmmJVHVKNs1d+emcQrtM6bsRhOjlFDSlxJte7pR4fGGXjeUXbnGpCe5v5dctCo7NNwWuiHsiXL255VjWbgC90eqAAhyYDFDpx7G7cwpcxmOAYc0XjvBRWTY/s64JePBlEnU6GfIwEgzm76hrTE5supugySWNrcI5jmFS5wFbOTAS+Va7YpkEtXBip3m6jnmowODVRfRHJgmYHnFRJtuUdsHbS3xOrI+I/KmQGinMSHCiwWrhgRL2x/EGGuHPuIKrM8q9OR1T9mvwZ1nD8PJglNdrDjIuoCxkdmhvBbBK/m4Ou2yfLLjEBUhnw==
1 Like

That’s my code:

const crypto = require('crypto');
const express = require('express');
const bodyParser = require('body-parser');

const port = 3000;

const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw+fZuC+0vDYTf8fYnCN6
71iHg98lPHBmafmqZqb+TUexn9sH6qNIBZ5SgYFxFK6dYXIuJ5uoORzihREvZVZP
8DphdeKOMUrMr6b+Cchb2qS8qz8WS7xtyLU9GnBn6M5mWfjkjQr1jbilH15Zvcpz
ECC8aPUAy2EbHpnr10if2IHkIAWLYD+0khpCjpWtsfuX+LxqzlqQVW9xc6z7tshK
eCSEa6Oh8+ia7Zlu0b+2xmy2Arb6xGl+s+Rnof4lsq9tZS6f03huc+XVTmd6H2We
WxFMfGyDCX2akEg2aAvx7231/6S0vBFGiX0C+3GbXlieHDplLGoODHUt5hxbPJnK
IwIDAQAB
-----END PUBLIC KEY-----`.replace(/\\n/g, "\n");


const app = express();
app.use(bodyParser.json());


app.post("/webhook", (req,res) => {
    
    const message = JSON.stringify(req.body);
    console.log('Body',message)
    const signature = req.header("fireblocks-signature");
    console.log('\nSignature:',signature)
    const verifier = crypto.createVerify('RSA-SHA512');
    verifier.write(message);
    verifier.end();

    const isVerified = verifier.verify(publicKey, signature, "base64");
    console.log("Verified:", isVerified);
    res.send("ok");
});

app.listen(port, () => {
    console.log(`Webhook running at port ${port}`);
})
1 Like

Hey @SlavaSereb ,

Thanks for sharing the codes with examples. Finally signature got matched.

The problem was with receiving the request body. Some of the params (such as createdAt, lastUpdated, amount etc)were getting converted to string. For this result was getting false.

Thanks again for sharing the codes, this really helped me to find the bug. :smiley:

Thanks for the update @iamyeasin

Glad to hear that it was resolved!

Hey @SlavaSereb, How are you?

We’re moving to production and we’re facing the webhook signature issue again.
Can you please check if this is the right public key for production? We’re getting false again. I checked the data types, looks alright to me.

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+6wd9OJQpK60ZI7qnZG
jjQ0wNFUHfRv85Tdyek8+ahlg1Ph8uhwl4N6DZw5LwLXhNjzAbQ8LGPxt36RUZl5
YlxTru0jZNKx5lslR+H4i936A4pKBjgiMmSkVwXD9HcfKHTp70GQ812+J0Fvti/v
4nrrUpc011Wo4F6omt1QcYsi4GTI5OsEbeKQ24BtUd6Z1Nm/EP7PfPxeb4CP8KOH
clM8K7OwBUfWrip8Ptljjz9BNOZUF94iyjJ/BIzGJjyCntho64ehpUYP8UJykLVd
CGcu7sVYWnknf1ZGLuqqZQt4qt7cUUhFGielssZP9N9x7wzaAIFcT3yQ+ELDu1SZ
dE4lZsf2uMyfj58V8GDOLLE233+LRsRbJ083x+e2mW5BdAGtGgQBusFfnmv5Bxqd
HgS55hsna5725/44tvxll261TgQvjGrTxwe7e5Ia3d2Syc+e89mXQaI/+cZnylNP
SwCCvx8mOM847T0XkVRX3ZrwXtHIA25uKsPJzUtksDnAowB91j7RJkjXxJcz3Vh1
4k182UFOTPRW9jzdWNSyWQGl/vpe9oQ4c2Ly15+/toBo4YXJeDdDnZ5c/O+KKadc
IMPBpnPrH/0O97uMPuED+nI6ISGOTMLZo35xJ96gPBwyG5s2QxIkKPXIrhgcgUnk
tSM7QYNhlftT4/yVvYnk0YcCAwEAAQ==
-----END PUBLIC KEY-----

Fireblocks-Signature

cOQRBf1tJbnDIgQDO0stq7zNniixNEcg1NYU9z6hm15L3HDut+J4X4UQotZi4AOqblKbphCEWV/Pe+KiyB2FcYd581dYwJ6reopyZdh77P+CZCjA/5FrG+Yo1jkhICP2D+x71tSooaKPgNVcLB5pcNcySAxVXPyb9QxyPVTCOzwX4QyjfGFQpDujCTH/UyeCXF95gcmJ1O+kzr0ialGQVueUPIE1h+2aLyiV4hk60GIZVpKH5su3OuBKzqa4uUtjt9sGRwXTEEXGrLaMfpgBJOOfU/ToQHvJkbJNmaI/86GKm59Snq1gHY62N0qOz4sR95KkGAx+aJZai37mGqIcN+Mj7dPI0aOwVHK6lJ9T+MtO2jq7OvGa+4HqlM4cVZFM1UEzXHUIFdJD4b0j99VTeNl5r6a59CuVrMCBI4+oHIT5a7eCqtlOhAb0JjQL6oI46nyLlQDUxWtrJdc7nrJdC1ZHX2xxeO+75NzbVB3RA8RuGtYtI3r9U7YEF/zWkJDaa2leGkN3kI1DJrcfr/WdEFd1GDYwzJ/On3HNjWhxFGOMejYH0u+j8tSzW4nroVJ1CHxTTMBrLTLnhqJMYid7kbRRyDyLGtN9CqLv+FQWFmEL2R9AcpsUHm/gxkGdNqm5QNBAPq8j3Hyax798s1k+xMfmGfWnueqPsVLiLKcDlS8=

Payload

{
  "type": "TRANSACTION_STATUS_UPDATED",
  "tenantId": "9a57c21c-78f7-55ff-94fe-7779f86e01f6",
  "timestamp": 1715843939213,
  "data": {
    "id": "6f456957-62fa-465d-83bc-89746819dd40",
    "createdAt": 1715843928396,
    "lastUpdated": 1715843928488,
    "assetId": "USDC_POLYGON_NXTB",
    "source": {
      "id": "",
      "type": "UNKNOWN",
      "name": "External",
      "subType": ""
    },
    "destination": {
      "id": "1",
      "type": "VAULT_ACCOUNT",
      "name": "Coins<>Fireblocks",
      "subType": ""
    },
    "amount": 0.00001,
    "networkFee": 0.003047226184502279,
    "netAmount": 0.00001,
    "sourceAddress": "0x4CDEbe3af68130e6506F136c32f9019dd006e1E4",
    "destinationAddress": "0x314a0179C54292f41360208F43E25bA8554Ec4e0",
    "destinationAddressDescription": "",
    "destinationTag": "",
    "status": "COMPLETED",
    "txHash": "0x5551830dff65a3f97a8b350be9e2255db36a56920e6b0c999176326118377713",
    "subStatus": "CONFIRMED",
    "signedBy": [],
    "createdBy": "",
    "rejectedBy": "",
    "amountUSD": 0,
    "addressType": "",
    "note": "",
    "exchangeTxId": "",
    "requestedAmount": 0.00001,
    "feeCurrency": "MATIC_POLYGON",
    "operation": "TRANSFER",
    "customerRefId": null,
    "numOfConfirmations": 1,
    "amountInfo": {
      "amount": "0.00001",
      "requestedAmount": "0.00001",
      "netAmount": "0.00001",
      "amountUSD": "0.00"
    },
    "feeInfo": {
      "networkFee": "0.003047226184502279",
      "gasPrice": "42.000002543"
    },
    "destinations": [],
    "externalTxId": null,
    "blockInfo": {
      "blockHeight": "57025793",
      "blockHash": "0x64de9afe7b1d6a0e72e2cc2e41f901d552819a74f14bd2549ff94b6017edd4d9"
    },
    "signedMessages": [],
    "index": 0,
    "assetType": "ERC20"
  }
}

Hi @iamyeasin,

I can confirm that this is the correct pubkey for production.
I was not able to verify the one you sent with the details you provided.

Are you sure that this is indeed the corresponding signature header?
I am testing the same code as shared previously with different events in my prod workspace and everything works fine.

Sharing for reference:

Signature:
cT79KO7wQx1q80UXIRUawmoKjLnfwLjJBuz/Hkb3EzTGPCZlnb5fT94YUCqbOkAoGGi74IlYE0enAXdYlkI3Qbg/7GmlEcjI31Z9+KsIYl4GCs/9VRyPQXhjWzxlbMvTkkb046pOTcqSuXDA1Zso1cxQowSaFWQ96EcGZM+0vycRvZQFZ9q9hZ7a6JpBxrSGXhkXmfc8l9JhdpcelT3TQSNFGBcXekBEX1ivbxN4Kcw7A9awTrsV3qdesZLoQ8B21Vv2xPPDEglBgOioXj+5M1LlXcExQMhuJZoWX3DYzfaqTBoN8LD2jwiNCOIWvEG3QODAKS4chCF2itVjyL5xcBRBZvknlijzSOFn5uCBy6qWNr/fcEiRzEgMLfCQ6eUgt+4LOxWabx8q8i/2xrBBT+/phtq39iAbDtCkoDic1Tr0zlB0GWFDi5jPb32Sh7E1ZvgYDhRfO4wRK+Hp2j7a1flV7xRg1B5P3SrR1jtEzaDR2aG6aJea8uSv4zfo9FFHSLXgK+8sYft6XGHjG5u4m595NxC3bSzaSGugdcoFr986hT9Y3HKcJvrhu4KNCss4vXNpO/ESnJ93pe7s/4f/1z/sHUCW4ztyql/8GUAW6yHKCz6fV0aJCq3eKra2h3JXAWA5DC4T16hnAWSE9Dxqnq+rKFCRU2WLDjnPlnLxtiQ=

Body:

{
  "type": "TRANSACTION_STATUS_UPDATED",
  "tenantId": "bb1903dd-77ca-5faa-9cd6-1860663dcf61",
  "timestamp": 1715851560584,
  "data": {
    "id": "bf53460a-bd46-4e10-9ef9-48f75f46f963",
    "createdAt": 1715851469530,
    "lastUpdated": 1715851550422,
    "assetId": "XTZ_TEST",
    "source": {
      "id": "0",
      "type": "VAULT_ACCOUNT",
      "name": "Main Vault",
      "subType": ""
    },
    "destination": {
      "id": "1",
      "type": "VAULT_ACCOUNT",
      "name": "Network Deposits",
      "subType": ""
    },
    "amount": 10,
    "networkFee": 0.00065,
    "netAmount": 10,
    "sourceAddress": "tz1f4eQxkT872adHhTKU9WGXw1TtbEKPcR2i",
    "destinationAddress": "tz1gpTK7PhbciqD76kGgjmf6eM7L7eXDANGQ",
    "destinationAddressDescription": "",
    "destinationTag": "",
    "status": "COMPLETED",
    "txHash": "op5RpjcLASV4fZYmo6bqznwuuSstgwcrfnVjB1nwGX5jLvKFb6h",
    "subStatus": "CONFIRMED",
    "signedBy": ["423d669d-a6d7-53c7-a8f6-743afad5364e"],
    "createdBy": "423d669d-a6d7-53c7-a8f6-743afad5364e",
    "rejectedBy": "",
    "amountUSD": 9.14364561,
    "addressType": "",
    "note": "",
    "exchangeTxId": "",
    "requestedAmount": 10,
    "feeCurrency": "XTZ_TEST",
    "operation": "TRANSFER",
    "customerRefId": null,
    "numOfConfirmations": 1,
    "amountInfo": {
      "amount": "10",
      "requestedAmount": "10",
      "netAmount": "10",
      "amountUSD": "9.14364561"
    },
    "feeInfo": { "networkFee": "0.00065" },
    "destinations": [],
    "externalTxId": null,
    "blockInfo": { "blockHeight": "6288527", "blockHash": null },
    "signedMessages": [],
    "index": 0,
    "assetType": "BASE_ASSET"
  }
}
1 Like

Hey @SlavaSereb , Found the Bug! :grinning:

In my Fireblocks dashboard vault name is created as Coins<>Fireblocks, I guess special characters are causing problems. Removed the special characters and signature got matched!

Thanks for confirming the key and sharing the payload as reference. :pray:

Hey @iamyeasin, good catch! I missed that one, sorry.

Thanks for the update!