กฎระเบียบการปกป้องข้อมูลและความเป็นส่วนตัวทั่วโลกมอบสิทธิให้บุคคลควบคุมข้อมูลของตน รวมถึงสิทธิในการขอให้ลบข้อมูล (มักเรียกว่า "สิทธิในการลบข้อมูล" หรือ "สิทธิในการลบ"). หากคุณเก็บข้อมูลส่วนบุคคลหรือ ข้อมูลที่สามารถระบุตัวตนได้ (PII) เช่น รหัสผู้ใช้ คุณมีหน้าที่ต้องปฏิบัติตามกรอบความเป็นส่วนตัวที่เกี่ยวข้องโดยการลบข้อมูลนี้อย่างถาวรเมื่อได้รับคำขอจากผู้ใช้ ข้อมูลเพิ่มเติมสามารถดูได้ที่ RTBF และผู้สร้าง.
พิจารณาทางเลือกอื่น
สำหรับข้อมูลส่วนบุคคลในที่เก็บข้อมูล ตัวเลือกการทำงานอัตโนมัติที่ง่ายที่สุดคือการกำหนดค่าเทมเพลตการลบ RTBF เมื่อ Roblox ประมวลผลคำขอ RTBF มันจะลบรายการที่ตรงกันในที่เก็บข้อมูลมาตรฐานหรือที่สั่งซื้อโดยอัตโนมัติโดยไม่ต้องให้คุณโฮสต์โค้ดที่กำหนดเอง เพื่อกำหนดค่าเทมเพลตและตรวจสอบรูปแบบที่รองรับ โปรดดูที่ สิทธิในการถูกลืมในที่เก็บข้อมูล (RTBF).
เว็บฮุคเวิร์กโฟลว์ในหน้านี้มีประโยชน์เมื่อ:
- คุณเก็บข้อมูลส่วนบุคคลนอกเหนือจากที่เก็บข้อมูล
- โครงสร้างข้อมูลของคุณหรือกรณีการใช้งานไม่ได้รับการสนับสนุนโดยเทมเพลตการลบ RTBF
- คุณต้องการการประมวลผลหรือตรรกะการลบที่กำหนดเอง
เวิร์กโฟลว์เว็บฮุคของผู้สร้าง
บทแนะนำนี้แสดงวิธีการ ตั้งค่าเว็บฮุค และสร้างบอท Discord ที่ทำงานในเครื่องซึ่งใช้ Open Cloud API สำหรับที่เก็บข้อมูล เพื่อทำให้การจัดการคำขอสิทธิในการลบข้อมูลเป็นไปโดยอัตโนมัติ เวิร์กโฟลว์สำหรับกระบวนการนี้มีดังนี้:
- Roblox Support ได้รับคำขอสิทธิในการลบข้อมูลจากผู้ใช้
- เว็บฮุคของ Roblox ถูกเรียกใช้งาน โดยมีรหัสผู้ใช้และรายการรหัสสถานที่เริ่มต้นสำหรับเกมที่พวกเขาเข้าร่วมในข้อมูล
- บอทของคุณฟังการแจ้งเตือนเว็บฮุคเหล่านี้ ตรวจสอบความถูกต้อง และใช้ Open Cloud API สำหรับที่เก็บข้อมูล เพื่อลบข้อมูล PII ที่เก็บอยู่ในที่เก็บข้อมูล
- บอทตอบกลับข้อความเว็บฮุคใน Discord ด้วยสถานะการลบ

กำหนดค่าเว็บฮุคด้วยการรวมกับบุคคลที่สาม
ก่อนที่จะสร้างบอท ให้ตั้งค่าเซิร์ฟเวอร์ด้วยการรวมเว็บฮุคในแอปพลิเคชันการส่งข้อความของบุคคลที่สาม จากนั้นใช้เซิร์ฟเวอร์เพื่อกำหนดค่าเว็บฮุคใน Creator Dashboard
ตั้งค่าเซิร์ฟเวอร์
ขั้นตอนต่อไปนี้แสดงวิธีการตั้งค่าเซิร์ฟเวอร์โดยใช้ Discord
สร้างเซิร์ฟเวอร์ Discord ใหม่ หากคุณไม่คุ้นเคยกับกระบวนการนี้ โปรดดูที่ Discord Support.
เซิร์ฟเวอร์จะสร้างช่อง #general โดยอัตโนมัติเป็นช่องเริ่มต้นของคุณ คลิกที่ไอคอน แก้ไขช่อง ของช่อง #general
ภายใต้ สิทธิ์ ให้ตั้งช่องเป็นส่วนตัว
สร้างการรวมเว็บฮุคกับเซิร์ฟเวอร์ใหม่ ตั้งชื่อเป็น RTBF Hook หากคุณไม่คุ้นเคยกับกระบวนการนี้ โปรดดูที่ Discord Support.
คัดลอก URL ของเว็บฮุคและเก็บไว้ในที่ปลอดภัย อนุญาตให้เฉพาะสมาชิกในทีมที่เชื่อถือได้เข้าถึง เนื่องจากการรั่วไหลของ URL อาจทำให้ผู้ไม่หวังดีส่งข้อความปลอมและอาจลบข้อมูลผู้ใช้ของคุณได้
กำหนดค่าเว็บฮุคใน Roblox
หลังจากได้รับ URL ของเซิร์ฟเวอร์บุคคลที่สามแล้ว ให้ใช้เพื่อ กำหนดค่าเว็บฮุค ใน Creator Dashboard ให้แน่ใจว่าคุณได้ทำการตั้งค่าดังต่อไปนี้:
- เพิ่ม URL ของเซิร์ฟเวอร์ Discord เป็น Webhook URL.
- รวม Secret ที่กำหนดเอง แม้ว่า Secret จะเป็นทางเลือกในการทำให้การกำหนดค่าครบถ้วน แต่คุณควรรวมไว้เพื่อป้องกันไม่ให้ผู้ไม่หวังดีแอบอ้างเป็น Roblox และลบข้อมูลของคุณ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการใช้ Secret โปรดดูที่ ตรวจสอบความปลอดภัยของเว็บฮุค.
- เลือก คำขอสิทธิในการลบข้อมูล ภายใต้ Triggers.
คุณสามารถทดสอบเว็บฮุคโดยใช้ปุ่ม ทดสอบการตอบกลับ เพื่อตรวจสอบว่าคุณได้รับการแจ้งเตือนในช่อง #general ของเซิร์ฟเวอร์ของคุณจาก Roblox หากคุณไม่ได้รับการแจ้งเตือน ให้ลองอีกครั้งหรือตรวจสอบการตั้งค่าเซิร์ฟเวอร์ของคุณเพื่อตรวจสอบข้อผิดพลาด
กำหนดค่าบอท
หลังจากที่คุณเพิ่มเว็บฮุคแล้ว ให้ใช้เพื่อกำหนดค่าบอทด้วยขั้นตอนต่อไปนี้ สำหรับข้อมูลเพิ่มเติม โปรดดูที่ เอกสารของ Discord.
- ไปที่ หน้าแอปพลิเคชัน.
- สร้างแอปพลิเคชันใหม่และตั้งชื่อเป็น RTBF Bot.
- ระบบจะเปลี่ยนเส้นทางคุณไปยังการตั้งค่าข้อมูลทั่วไปของบอท คัดลอกและบันทึก ID ของแอปพลิเคชันในที่ปลอดภัย
- ภายใต้เมนูการตั้งค่า ให้เลือก OAuth2.
- ไปที่การตั้งค่า OAuth2
- เปิดใช้งาน bot scope จะมีรายการสิทธิ์บอทเพิ่มเติมแสดงขึ้น
- เพิ่มสิทธิ์ Administrator บันทึกการเปลี่ยนแปลงของคุณ
- บันทึก URL ที่สร้างขึ้น
- คัดลอก URL ที่สร้างขึ้น ทิ้งหน้าการตั้งค่าแอปพลิเคชันไว้ไม่ปิด
- ไปที่ URL ที่สร้างขึ้น เลือกเซิร์ฟเวอร์เป้าหมาย คลิกปุ่ม ดำเนินการต่อ และจากนั้นปุ่ม อนุญาต
- กลับไปที่หน้าการตั้งค่าแอปพลิเคชันและไปที่การตั้งค่าบอท
- ภายใต้ส่วน Privileged Gateway Intents ให้เปิดใช้งาน Message Content Intent.
- ในการตั้งค่าบอท > ส่วน Build-A-Bot ให้บันทึกโทเค็นบอทในที่ปลอดภัยสำหรับขั้นตอนถัดไป หากคุณไม่เห็นโทเค็น ให้คลิกปุ่ม รีเซ็ตโทเค็น เพื่อสร้างโทเค็นใหม่
สร้างคีย์ Open Cloud API
เพื่อให้บอทของคุณเข้าถึงที่เก็บข้อมูลของคุณสำหรับการเก็บข้อมูล PII ของผู้ใช้ ให้ สร้างคีย์ Open Cloud API ที่สามารถเข้าถึงเกมของคุณและเพิ่มสิทธิ์ Delete Entry ของที่เก็บข้อมูลสำหรับการลบข้อมูล หากคุณใช้ที่เก็บข้อมูลที่สั่งซื้อสำหรับการเก็บข้อมูล PII คุณยังต้องเพิ่มสิทธิ์ Write ของที่เก็บข้อมูลที่สั่งซื้อ หลังจากเสร็จสิ้น ให้คัดลอกและบันทึกคีย์ API ในที่ปลอดภัยเพื่อใช้ในขั้นตอนถัดไป
รับตัวระบุของเกมและสถานที่
เพื่อให้บอทสามารถค้นหาข้อมูล PII ที่ผู้ใช้ขอให้ลบได้ ให้รับตัวระบุต่อไปนี้ของเกมทั้งหมดที่คุณตั้งใจจะใช้บอท:
- Universe ID ตัวระบุที่ไม่ซ้ำกันของเกมของคุณ
- Start Place ID ตัวระบุที่ไม่ซ้ำกันของสถานที่เริ่มต้นของเกม
ในการรับตัวระบุเหล่านี้:
ไปที่ Creator Dashboard.
เลื่อนเมาส์ไปที่ภาพขนาดย่อของเกม คลิกที่ปุ่ม ⋯ และเลือก คัดลอก Universe ID และ คัดลอก Start Place ID ตามลำดับ

เพิ่มสคริปต์
หลังจากที่คุณตั้งค่าเว็บฮุค บอท และคีย์ API สำหรับที่เก็บข้อมูลเสร็จสิ้นแล้ว ให้เพิ่มพวกเขาไปยังสคริปต์ที่ใช้ตรรกะการทำงานอัตโนมัติของบอท ตัวอย่างต่อไปนี้ใช้ Python 3
ติดตั้งไลบรารี Python โดยใช้คำสั่งต่อไปนี้:
ติดตั้งไลบรารีpip3 install discordpip3 install requestspip3 install urllib3==1.26.6คัดลอกและบันทึกสคริปต์ต่อไปนี้ซึ่งสอดคล้องกับส่วนต่าง ๆ ของตรรกะบอทในไดเรกทอรีเดียวกัน:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# พจนานุกรมของ Start place ID ไปยัง# (universe ID, รายการของ (ชื่อที่เก็บข้อมูล, ขอบเขต, และคีย์รายการ)) สำหรับ# ที่เก็บข้อมูลมาตรฐาน# ข้อมูลผู้ใช้ที่เก็บอยู่ภายใต้รายการเหล่านี้จะถูกลบSTANDARD_DATA_STORE_ENTRIES = {# Start Place ID111111111: (# Universe ID222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# พจนานุกรมของ Start place ID ไปยัง# (universe ID, รายการของ (ชื่อที่เก็บข้อมูล, ขอบเขต, และคีย์รายการ)) สำหรับ# ที่เก็บข้อมูลที่สั่งซื้อ# ข้อมูลผู้ใช้ที่เก็บอยู่ภายใต้รายการเหล่านี้จะถูกลบORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""เรียกใช้ Open Cloud API ของที่เก็บข้อมูลเพื่อลบรายการทั้งหมดสำหรับ user_id ที่กำหนดในSTANDARD_DATA_STORE_ENTRIES คืนค่ารายการของการลบที่สำเร็จและความล้มเหลวในการลบ"""def delete_standard_data_stores(user_id, start_place_ids):successes = defaultdict(list)failures = defaultdict(list)for owned_start_place_id in bot_config.STANDARD_DATA_STORE_ENTRIES:if owned_start_place_id not in start_place_ids:continueuniverse_id, universe_entries = bot_config.STANDARD_DATA_STORE_ENTRIES[owned_start_place_id]for (data_store_name, scope, entry_key) in universe_entries:entry_key = entry_key.replace("{user_id}", user_id)response = requests.delete(f"https://apis.roblox.com/datastores/v1/universes/{universe_id}/standard-datastores/datastore/entries/entry",headers={"x-api-key": bot_config.OPEN_CLOUD_API_KEY},params={"datastoreName": data_store_name,"scope": scope,"entryKey": entry_key})if response.status_code in [200, 204]:successes[owned_start_place_id].append((data_store_name, scope, entry_key))else:failures[owned_start_place_id].append((data_store_name, scope, entry_key))return successes, failures"""เรียกใช้ Open Cloud API ของที่เก็บข้อมูลที่สั่งซื้อเพื่อลบรายการทั้งหมดสำหรับ user_id ที่กำหนดในORDERED_DATA_STORE_ENTRIES คืนค่ารายการของการลบที่สำเร็จและความล้มเหลวในการลบ"""def delete_ordered_data_stores(user_id, start_place_ids):successes = defaultdict(list)failures = defaultdict(list)for owned_start_place_id in bot_config.ORDERED_DATA_STORE_ENTRIES:if owned_start_place_id not in start_place_ids:continueuniverse_id, universe_entries = bot_config.ORDERED_DATA_STORE_ENTRIES[owned_start_place_id]for (data_store_name, scope, entry_key) in universe_entries:entry_key = entry_key.replace("{user_id}", user_id)response = requests.delete(f"https://apis.roblox.com/ordered-data-stores/v1/universes/{universe_id}/orderedDatastores/{data_store_name}/scopes/{scope}/entries/{entry_key}",headers={"x-api-key": bot_config.OPEN_CLOUD_API_KEY})if response.status_code in [200, 204, 404]:successes[owned_start_place_id].append((data_store_name, scope, entry_key))else:failures[owned_start_place_id].append((data_store_name, scope, entry_key))return successes, failuresmessage_parser.pyimport timeimport hmacimport hashlibimport reimport base64import bot_config"""แยกข้อความที่ได้รับสำหรับลายเซ็นและเวลาประทับของ Roblox ฟุตเตอร์จะถูกตั้งค่าเฉพาะถ้าคุณกำหนดเว็บฮุค Secret"""def parse_footer(message):if not message.embeds[0].footer or \not message.embeds[0].footer.text:return "", 0footer_match = re.match(r"Roblox-Signature: (.*), Timestamp: (.*)",message.embeds[0].footer.text)if not footer_match:return "", 0else:signature = footer_match.group(1)timestamp = int(footer_match.group(2))return signature, timestamp"""ตรวจสอบลายเซ็น Roblox ด้วย Secret ที่กำหนดเพื่อตรวจสอบความถูกต้อง"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# ป้องกันการโจมตีแบบ replay ภายในหน้าต่างเวลา 300 วินาทีrequest_timestamp_ms = timestamp * 1000window_time_ms = 300 * 1000oldest_timestamp_allowed = round(time.time() * 1000) - window_time_msif request_timestamp_ms < oldest_timestamp_allowed:return False# ตรวจสอบลายเซ็นtimestamp_message = "{}.{}".format(timestamp, message.embeds[0].description)digest = hmac.new(bot_config.ROBLOX_WEBHOOK_SECRET.encode(),msg=timestamp_message.encode(),digestmod=hashlib.sha256).digest()validated_signature = base64.b64encode(digest).decode()if signature != validated_signature:return False# ลายเซ็นที่ถูกต้องreturn True"""แยกข้อความเว็บฮุคที่ได้รับใน Discord ดึงรหัสผู้ใช้ ป้องกันการโจมตีแบบ replayตามเวลาประทับที่ได้รับ และตรวจสอบลายเซ็น Roblox ด้วย Secret ที่กำหนดเพื่อตรวจสอบความถูกต้อง"""def parse_message(message):# แยกข้อความที่ได้รับสำหรับรหัสผู้ใช้และรหัสเกมif len(message.embeds) != 1 or \not message.embeds[0].description:return "", []description_match = re.match(r"You have received a new notification for Right to Erasure for the User Id: (.*) in " +r"the game\(s\) with Ids: (.*)",message.embeds[0].description)if not description_match:return "", []user_id = description_match.group(1)start_place_ids = set(int(item.strip()) for item in description_match.group(2).split(","))signature, timestamp = parse_footer(message)if validate_signature(message, signature, timestamp):return user_id, start_place_idselse:return "", []discord_bot.pyimport discordimport bot_configimport data_stores_apiimport message_parserdef run():intents = discord.Intents.default()intents.message_content = Trueclient = discord.Client(intents=intents)@client.eventasync def on_ready():print(f"{client.user} is listening to Right to Erasure messages")"""ตัวจัดการสำหรับข้อความเว็บฮุคจาก Roblox"""@client.eventasync def on_message(message):# แยกและตรวจสอบข้อความuser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# ลบข้อมูลผู้ใช้จากที่เก็บข้อมูลมาตรฐาน[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"ลบข้อมูลที่เก็บข้อมูลมาตรฐานสำหรับ " +f"รหัสผู้ใช้: {user_id}, ข้อมูล: {dict(successes)}")if failures:await message.reply(f"ไม่สามารถลบข้อมูลที่เก็บข้อมูลมาตรฐานสำหรับ " +f"รหัสผู้ใช้: {user_id}, ข้อมูล: {dict(failures)}")# ลบข้อมูลผู้ใช้จากที่เก็บข้อมูลที่สั่งซื้อ[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"ลบข้อมูลที่เก็บข้อมูลที่สั่งซื้อสำหรับ " +f"รหัสผู้ใช้: {user_id}, ข้อมูล: {dict(successes)}")if failures:await message.reply(f"ไม่สามารถลบข้อมูลที่เก็บข้อมูลที่สั่งซื้อสำหรับ " +f"รหัสผู้ใช้: {user_id}, ข้อมูล: {dict(failures)}")client.run(bot_config.BOT_TOKEN)if __name__ == "__main__":run()ในไฟล์ bot_config.py สำหรับการกำหนดค่าหลักของบอท:
- ตั้งค่า BOT_TOKEN เป็นโทเค็นที่สร้างโดยบอทของคุณ
- ตั้งค่า OPEN_CLOUD_API_KEY เป็นคีย์ API ที่คุณสร้างขึ้น
- ตั้งค่า ROBLOX_WEBHOOK_SECRET เป็น Secret ที่คุณตั้งค่าเมื่อกำหนดค่าเว็บฮุคใน Creator Dashboard
- ในพจนานุกรม STANDARD_DATA_STORE_ENTRIES และ ORDERED_DATA_STORE_ENTRIES สำหรับการค้นหาที่เก็บข้อมูลของแต่ละระเบียนที่จะลบ:
- เพิ่ม Start Place IDs ที่คุณคัดลอกเป็นคีย์
- เพิ่ม Universe IDs เป็นองค์ประกอบแรกของค่าทูเพิล
- แทนที่องค์ประกอบที่สองของทูเพิลด้วยชื่อ ขอบเขต ชื่อคีย์รายการ และรหัสผู้ใช้ที่เกี่ยวข้องของที่เก็บข้อมูลของคุณ หากคุณใช้โครงสร้างข้อมูลที่แตกต่างกัน ให้แก้ไขให้ตรงกับโครงสร้างข้อมูลของคุณเอง
รันคำสั่งต่อไปนี้เพื่อเรียกใช้บอท:
รันบอท Discordpython3 discord_bot.pyบอทจะเริ่มฟังและตรวจสอบเว็บฮุคของ Roblox สำหรับคำขอสิทธิในการลบข้อมูลและเรียกใช้ Open Cloud endpoint เพื่อลบที่เก็บข้อมูลที่เกี่ยวข้อง
ทดสอบ
คุณสามารถสร้างและรันข้อความทดสอบเพื่อตรวจสอบว่าโปรแกรมที่กำหนดเองของคุณสามารถจัดการคำขอสิทธิในการลบข้อมูลและลบข้อมูล PII ได้อย่างถูกต้อง:
ส่งคำขอ HTTP POST ไปยังเซิร์ฟเวอร์เว็บฮุค Discord ของคุณด้วยเนื้อหาคำขอดังต่อไปนี้:
ตัวอย่างคำขอcurl -X POST {serverUrl}-H 'Content-Type: application/json'-d '{"embeds":[{"title":"RightToErasureRequest","description":"คุณได้รับการแจ้งเตือนใหม่สำหรับสิทธิในการลบข้อมูลสำหรับรหัสผู้ใช้: {userIds} ในเกมที่มีรหัส: {gameIds}","footer":{"icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png","text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"}}]}'หากคุณมีเว็บฮุค Secret:
- สร้าง Roblox-Signature โดยการเข้ารหัส HMAC-SHA256 ด้วยคีย์เว็บฮุค Secret ของคุณ
- ตั้งเวลาในปัจจุบันโดยใช้เวลาประทับ UTC ในวินาทีเป็น Timestamp
รวบรวม description ในรูปแบบต่อไปนี้:
รูปแบบฟิลด์คำอธิบาย{Timestamp}. คุณได้รับการแจ้งเตือนใหม่สำหรับสิทธิในการลบข้อมูลสำหรับรหัสผู้ใช้: {userId} ในเกมที่มีรหัส: {gameIds}`.ตัวอย่างเช่น:
ตัวอย่างฟิลด์คำอธิบาย1683927229. คุณได้รับการแจ้งเตือนใหม่สำหรับสิทธิในการลบข้อมูลสำหรับรหัสผู้ใช้: 2425654247 ในเกมที่มีรหัส: 10539205763, 13260950955
โปรแกรมของคุณควรสามารถระบุได้ว่าข้อความของคุณมาจากแหล่งที่มาของ Roblox อย่างเป็นทางการ เนื่องจากคุณได้เข้ารหัสข้อความด้วย Secret ของคุณ มันควรจะลบข้อมูล PII ที่เกี่ยวข้องกับคำขอของคุณ
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "คุณได้รับการแจ้งเตือนใหม่สำหรับสิทธิในการลบข้อมูลสำหรับรหัสผู้ใช้: 2425654247 ในเกมที่มีรหัส: 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}