Verifying a Roblox-Signature without the payload, or how do I get the full payload?

I’m trying to verify the authenticity of a Roblox webhook request. It sends the request to my Discord webhook, where I am now attempting to reconstruct the Roblox-Signature using my encoding key. But, apparently to do that, I need the full request payload, which, well, doesn’t send.

This is what I mean:

But my discord bot has no problem extracting the signature and timestamp itself:
image

but when it comes to verifying the integrity of the request, it simply can’t regenerate the Roblox-Signature accurately.

//my code to check the roblox-signature
function verifyRequest(signature, timestamp) {
    //check if the timestamp is within a valid time window to prevent replay attacks
    let currentTime = Date.now();
    let requestTime = new Date(timestamp).getTime();
    if (Math.abs(currentTime - requestTime) > 300000) { // 5-minute window
        return false; // Reject request if timestamp is too old
    }

    //generate the HMAC hash using the secret and timestamp
    let hash = crypto.createHmac("sha256", gdprWebhookSecret)
        .update(timestamp)
        .digest("base64");

    //ensure both buffers are the same length before comparing
    let hashBuffer = Buffer.from(hash, "base64");
    let signatureBuffer = Buffer.from(signature, "base64");
    if (hashBuffer.length !== signatureBuffer.length) {
        return false; //reject if lengths don't match
    }

    //perform a timing-safe comparison
    return crypto.timingSafeEqual(hashBuffer, signatureBuffer);
}

Does anyone know how I can get the full data needed to regenerate the Roblox-Signature accurately? Apparently, I also need the:

  • NotificationId
  • EventType
  • EventTime
  • EventPayload

Any help is appreciated.

Where is Roblox-Signature documented?

Not entirely sure; I was looking under the Automating Right to Erasure docs as that’s the only place I can find it documented (im not using the code there though).

I think it’s just a piece of data sent along the request you can use to verify it’s from Roblox - you generate a hash key, give it to the Roblox webhook, then when you receive a request from the Roblox webhook, you also generate a hash with the same data. If the hashes match, well, then you know it’s from Roblox and not a bad actor, since only you and Roblox have the hash key.

The problem is that the hash generated by Roblox is generated using the 4 pieces of data I mentioned before, and not all of those pieces of data come through on the Discord webhook.

1 Like

If you visually compare the signature your code generates, and the Roblox-Signature, do they look similar in format? Maybe you are applying the Base64 encoding and the hashing in the wrong order? This could be a Roblox bug if you’re following all the correct steps.

I’m looking at the Docs page you linked, the validate_signature function they provide just uses the Timestamp and part of the Message body, where have you gotten NotificationId etc from?

EDIT: Are your strings encoded in UTF-8? One of the only things I can see that are not present in your code. EDIT FOR THE EDIT: This shouldn’t matter unless your strings have non-ASCII characters actually

1 Like

It was my bad, I think I misread the part about how the Roblox-Signature is actually encoded (you’re right, only uses the timestamp and message body). In the end, I just took the code snippet on the docs and translated it into JavaScript.


Since I misread it and confused myself, I then went to the AI Assistant on the docs, because I thought “oh, well, this ai which has access to all the docs should know” and what do you know? it told me the wrong stuff.

Thanks for the help.


In case anyone wants it, this is the verification code in JavaScript:

function verifyRequest(message, signature, timestamp) {
    if (!message || !signature || !timestamp) {
        return false;
    }

    //prevents replay attack within 300 seconds window
    let requestTimestampMs = timestamp * 1000;
    let windowTimeMs = 300 * 1000;
    let oldestTimestampAllowed = Date.now() - windowTimeMs;
    if (requestTimestampMs < oldestTimestampAllowed) {
        return false;
    }

    //validates signature
    let timestampMessage = `${timestamp}.${message.embeds[0].description}`;
    let hmac = crypto.createHmac("sha256", gdprWebhookSecret);
    hmac.update(timestampMessage);
    let validatedSignature = hmac.digest("base64");
    
    if (signature !== validatedSignature) {
        return false;
    }

    //valid signature
    return true;
}
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.