Guides
Roblox Open Cloud Webhooks: Complete Setup and Security Guide
Roblox Open Cloud webhooks allow Roblox to send notifications directly to an external application when supported events occur.
Instead of repeatedly checking Roblox for changes, your application can receive an HTTP request automatically.
A basic setup looks like this:
Roblox
↓
Webhook notification
↓
Your HTTPS endpoint
↓
Verify signature
↓
Process event
↓
Database / Discord / Application
This is different from sending a webhook from inside a Roblox game using HttpService.
For outbound Roblox requests, read:
Complete Roblox Webhooks Guide
For Discord logging from inside your game, see:
Freeflow Team•Sep 28, 2026•10 min readWhat Are Roblox Open Cloud Webhooks?
Traditional APIs work by asking a service for information.
For example:
Your App
↓
"Has anything changed?"
↓
Roblox API
You might have to repeat that request every few minutes.
A webhook reverses this relationship.
Something happens on Roblox
↓
Roblox sends the event
↓
Your server receives it
This is more suitable for events that need to be processed shortly after they occur.
Where Do You Configure Roblox Webhooks?
Webhook notifications can be configured through Roblox Creator Hub.
A general setup is:
- Open Creator Hub.
- Select your experience.
- Open its configuration.
- Select Webhooks.
- Add a webhook.
- Enter your webhook URL.
- Give the webhook a name.
- Add a secret.
- Choose supported triggers.
- Test the endpoint.
- Save your configuration.
For a group-owned experience, webhook configuration permissions may depend on the group owner and the supported event.
Roblox Webhook Endpoint Requirements
Your endpoint should:
- Be publicly accessible
- Accept HTTP POST requests
- Use HTTPS
- Process JSON
- Return a successful 2XX HTTP response quickly
- Verify incoming requests
- Protect against replay attacks
Your endpoint should not perform slow work before responding.
A better architecture is:
Roblox
↓
Webhook endpoint
↓
Validate request
↓
Queue event
↓
Return HTTP 200
↓
Process asynchronously
Basic Node.js Roblox Webhook Receiver
Install Express:
npm init -y
npm install express
Create:
server.js
Add:
const express = require("express");
const app = express();
app.use(express.json());
app.post("/roblox-webhook", (req, res) => {
console.log("Webhook received");
console.log(req.headers);
console.log(req.body);
res.sendStatus(200);
});
app.listen(3000, () => {
console.log("Listening on port 3000");
});
This is useful for learning, but it is not enough for production.
You should verify the webhook's Roblox signature.
Why Roblox Webhook Signatures Matter
Imagine your endpoint is:
https://example.com/roblox-webhook
Without authentication, somebody else could potentially send fake requests to it.
That becomes dangerous if incoming webhook events perform actions such as:
- Deleting data
- Changing account information
- Triggering moderation
- Sending staff alerts
- Updating external databases
Roblox supports webhook secrets so your application can verify that an event really came from Roblox.
Roblox-Signature Header
With a webhook secret configured, the request includes a signature with a structure similar to:
t=TIMESTAMP,v1=SIGNATURE
For example:
t=1700000000,v1=BASE64_SIGNATURE
Your application can validate this.
How Roblox Webhook Signature Verification Works
The process is:
timestamp
+
"."
+
raw request JSON
Example conceptual signing string:
1700000000.{"example":"data"}
Then calculate:
HMAC-SHA256(
secret,
signingString
)
The result is converted to Base64.
Your calculated signature should match the v1 signature Roblox sent.
Important: Preserve the Raw Request Body
For signature verification, you should verify the exact body Roblox sent.
If your framework parses and then re-serializes JSON differently, whitespace or formatting may change.
That can cause a signature mismatch.
A production receiver should capture the raw body.
Secure Express Webhook Receiver
Install:
npm install express
Then:
const express = require("express");
const crypto = require("crypto");
const app = express();
const WEBHOOK_SECRET = process.env.ROBLOX_WEBHOOK_SECRET;
app.use(
express.json({
verify: (req, res, buffer) => {
req.rawBody = buffer.toString("utf8");
},
}),
);
Now create the verification function.
function parseRobloxSignature(header) {
if (!header) {
return null;
}
const parts = header.split(",");
const timestampPart = parts.find((part) => part.startsWith("t="));
const signaturePart = parts.find((part) => part.startsWith("v1="));
if (!timestampPart || !signaturePart) {
return null;
}
return {
timestamp: timestampPart.substring(2),
signature: signaturePart.substring(3),
};
}
Generate the Expected Roblox Signature
function createExpectedSignature(timestamp, rawBody) {
const signingString = `${timestamp}.${rawBody}`;
return crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(signingString)
.digest("base64");
}
Timing-Safe Signature Comparison
Avoid basic string equality for security-sensitive signature checks.
function safeCompare(expected, received) {
const expectedBuffer = Buffer.from(expected);
const receivedBuffer = Buffer.from(received);
if (expectedBuffer.length !== receivedBuffer.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}
Protect Against Replay Attacks
A valid webhook request could potentially be captured and resent later.
Check the timestamp.
Example five-minute window:
function validTimestamp(timestamp) {
const requestTime = Number(timestamp);
if (!Number.isFinite(requestTime)) {
return false;
}
const now = Math.floor(Date.now() / 1000);
const difference = Math.abs(now - requestTime);
return difference <= 300;
}
Complete Roblox Webhook Verification
function verifyRobloxWebhook(req) {
const signatureHeader = req.headers["roblox-signature"];
const parsed = parseRobloxSignature(signatureHeader);
if (!parsed) {
return false;
}
if (!validTimestamp(parsed.timestamp)) {
return false;
}
const expected = createExpectedSignature(parsed.timestamp, req.rawBody);
return safeCompare(expected, parsed.signature);
}
Now use it:
app.post("/roblox-webhook", (req, res) => {
if (!verifyRobloxWebhook(req)) {
return res.status(401).send("Invalid webhook");
}
console.log("Verified Roblox webhook");
console.log(req.body);
res.sendStatus(200);
});
Complete Node.js Example
Put everything together:
const express = require("express");
const crypto = require("crypto");
const app = express();
const PORT = process.env.PORT || 3000;
const WEBHOOK_SECRET = process.env.ROBLOX_WEBHOOK_SECRET;
app.use(
express.json({
verify: (req, res, buffer) => {
req.rawBody = buffer.toString("utf8");
},
}),
);
function parseRobloxSignature(header) {
if (!header) {
return null;
}
const parts = header.split(",");
const timestampPart = parts.find((part) => part.startsWith("t="));
const signaturePart = parts.find((part) => part.startsWith("v1="));
if (!timestampPart || !signaturePart) {
return null;
}
return {
timestamp: timestampPart.substring(2),
signature: signaturePart.substring(3),
};
}
function validTimestamp(timestamp) {
const requestTime = Number(timestamp);
if (!Number.isFinite(requestTime)) {
return false;
}
const currentTime = Math.floor(Date.now() / 1000);
return Math.abs(currentTime - requestTime) <= 300;
}
function createExpectedSignature(timestamp, rawBody) {
const signingString = `${timestamp}.${rawBody}`;
return crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(signingString)
.digest("base64");
}
function safeCompare(first, second) {
const firstBuffer = Buffer.from(first);
const secondBuffer = Buffer.from(second);
if (firstBuffer.length !== secondBuffer.length) {
return false;
}
return crypto.timingSafeEqual(firstBuffer, secondBuffer);
}
function verifyRobloxWebhook(req) {
const header = req.headers["roblox-signature"];
const values = parseRobloxSignature(header);
if (!values) {
return false;
}
if (!validTimestamp(values.timestamp)) {
return false;
}
const expected = createExpectedSignature(values.timestamp, req.rawBody);
return safeCompare(expected, values.signature);
}
app.post("/roblox-webhook", async (req, res) => {
if (!verifyRobloxWebhook(req)) {
return res.status(401).json({
error: "Invalid signature",
});
}
const event = req.body;
console.log("Verified Roblox webhook:", event);
/*
* Put event into your queue,
* database or processing system.
*/
res.sendStatus(200);
});
app.listen(PORT, () => {
console.log(`Webhook server listening on ${PORT}`);
});
Environment variable:
ROBLOX_WEBHOOK_SECRET=YOUR_RANDOM_SECRET
Python Roblox Webhook Verification Example
You can also create a receiver in Python.
Install Flask:
pip install flask
Create:
from flask import Flask
from flask import request
import base64
import hashlib
import hmac
import time
app = Flask(__name__)
WEBHOOK_SECRET = "YOUR_SECRET"
Parse the signature:
def parse_signature(header):
if not header:
return None, None
timestamp = None
signature = None
parts = header.split(",")
for part in parts:
if part.startswith("t="):
timestamp = part[2:]
if part.startswith("v1="):
signature = part[3:]
return timestamp, signature
Generate expected signature:
def generate_signature(
timestamp,
raw_body
):
message = (
timestamp +
"." +
raw_body
)
digest = hmac.new(
WEBHOOK_SECRET.encode(),
message.encode(),
hashlib.sha256
).digest()
return base64.b64encode(
digest
).decode()
Timestamp check:
def valid_timestamp(timestamp):
try:
timestamp = int(timestamp)
except:
return False
now = int(time.time())
difference = abs(
now - timestamp
)
return difference <= 300
Endpoint:
@app.route(
"/roblox-webhook",
methods=["POST"]
)
def roblox_webhook():
signature_header = (
request.headers.get(
"roblox-signature"
)
)
timestamp, signature = (
parse_signature(
signature_header
)
)
if not timestamp or not signature:
return (
"Missing signature",
401
)
if not valid_timestamp(timestamp):
return (
"Expired webhook",
401
)
raw_body = (
request
.get_data(
as_text=True
)
)
expected = generate_signature(
timestamp,
raw_body
)
if not hmac.compare_digest(
expected,
signature
):
return (
"Invalid signature",
401
)
payload = request.get_json()
print(
"Verified webhook:",
payload
)
return "", 200
Run:
if __name__ == "__main__":
app.run(
port=3000
)
Forward Roblox Webhooks to Discord
Once you've verified a Roblox webhook, you can send a staff notification.
Node example:
async function sendToDiscord(title, message) {
const webhook = process.env.DISCORD_WEBHOOK;
await fetch(webhook, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
embeds: [
{
title,
description: message,
timestamp: new Date().toISOString(),
},
],
}),
});
}
After verification:
await sendToDiscord("Roblox Webhook", JSON.stringify(req.body, null, 2));
For a fuller Discord setup, read:
Store Roblox Webhook Events in a Database
A simplified database structure could be:
webhook_events
---------------------------------
id
event_type
received_at
payload
processed
Example SQL:
CREATE TABLE webhook_events (
id BIGSERIAL PRIMARY KEY,
event_type TEXT,
received_at TIMESTAMP DEFAULT NOW(),
payload JSONB NOT NULL,
processed BOOLEAN DEFAULT FALSE
);
Insert:
INSERT INTO webhook_events (
event_type,
payload
)
VALUES (
$1,
$2
);
Storing the original payload can make debugging easier.
Be careful about retaining personal information unnecessarily.
Make Roblox Webhook Processing Idempotent
A webhook handler should be able to receive the same event twice without causing a serious problem.
Bad:
Webhook arrives
→ give reward
Webhook arrives again
→ give same reward again
Better:
Webhook arrives
→ check event ID
→ already processed?
→ ignore
→ new?
→ process
→ record ID
Conceptual JavaScript:
if (await eventAlreadyProcessed(event.id)) {
return res.sendStatus(200);
}
await processEvent(event);
await markEventProcessed(event.id);
Return a Successful Response Quickly
Your endpoint should avoid waiting for slow tasks.
Bad:
Webhook
→ huge database query
→ external API
→ Discord
→ email
→ analytics
→ response
Better:
Webhook
→ verify
→ queue
→ 200 OK
Worker
→ database
→ Discord
→ email
→ analytics
Example:
app.post("/roblox-webhook", (req, res) => {
if (!verifyRobloxWebhook(req)) {
return res.sendStatus(401);
}
queue.push(req.body);
res.sendStatus(200);
});
Common Roblox Webhook Errors
Roblox Cannot Reach the Endpoint
Make sure your endpoint is publicly accessible.
This will not work:
http://localhost:3000
Roblox cannot access your computer's localhost.
You need something publicly reachable such as:
https://api.example.com/roblox-webhook
Invalid Signature
Common causes include:
- Wrong secret
- Incorrect timestamp
- Modified request body
- Re-encoding JSON before verification
- Incorrect Base64 conversion
- Incorrect HMAC algorithm
Endpoint Takes Too Long
Return a successful response quickly and process heavy work afterwards.
Repeated Events
Design your handlers to be idempotent.
Fake Requests
Never perform important actions before validating the webhook signature.
Roblox Webhook Security Checklist
Before using a webhook in production:
- Use HTTPS
- Add a webhook secret
- Verify
roblox-signature - Use HMAC-SHA256
- Verify the raw request body
- Compare signatures safely
- Validate the timestamp
- Reject old requests
- Validate the payload
- Log errors
- Prevent duplicate processing
- Store secrets in environment variables
- Return 2XX quickly
- Keep sensitive actions server-side
Roblox Webhooks vs Roblox API Polling
Polling
Your server
→ Roblox API
→ no change
Your server
→ Roblox API
→ no change
Your server
→ Roblox API
→ event found
Webhook
Event happens
→ Roblox contacts you
Webhooks are usually preferable for supported event-driven workflows because you don't need to continually check for changes.
When Should You Use Roblox Webhooks?
Webhooks make sense when:
- An external application needs near-real-time information
- You need automated platform notifications
- You're connecting Roblox with another service
- You're creating a creator dashboard
- You're building automated workflows
For events produced directly by your game server, HttpService may instead be the right tool.
Read:
Frequently Asked Questions
Does Roblox support webhooks?+
Yes. Roblox provides webhook notifications through its cloud tooling for supported events, alongside HttpService for outbound requests from experiences.
Where are Roblox webhooks configured?+
Supported webhook notifications can be configured through Creator Hub for an experience.
Can Roblox webhooks go to Discord?+
Roblox supports Discord as a webhook destination for supported Creator Hub webhook notifications. You can also receive events at your own endpoint and forward selected information to Discord.
What is roblox-signature?+
roblox-signature allows your custom webhook endpoint to verify that a webhook was generated using the secret configured for the Roblox webhook.
What algorithm does Roblox use for webhook signatures?+
Roblox webhook signature verification uses HMAC with SHA-256 and Base64 encoding of the result.
Why should I verify the webhook timestamp?+
Checking the timestamp helps protect your application against replayed requests.
Can localhost receive Roblox webhooks?+
No. Roblox needs to reach a publicly accessible endpoint.
More Roblox Webhook Guides
Continue with:
Roblox Webhooks: Complete Guide
Roblox Discord Webhooks: Lua & Discord Guide
You can also explore Roblox scripts and development assets on Freeflow.