Las regulaciones globales de protección de datos y privacidad otorgan a las personas el derecho a controlar sus datos, incluido el derecho a solicitar su eliminación (a menudo denominado "derecho a la eliminación" o "derecho a borrar"). Si almacenas datos personales o Información de Identificación Personal (PII), como ID de usuario, eres responsable de cumplir con el/los marco(s) de privacidad aplicable(s) eliminando permanentemente esta información al recibir una solicitud del usuario. Se puede encontrar más información en RTBF y creadores.
En lugar de manejar solicitudes manualmente, puedes configurar un webhook y usar un bot dentro de una aplicación de mensajería de terceros para automatizar el proceso. Como los almacenes de datos son la forma más común de almacenar datos de PII, este tutorial proporciona un ejemplo de cómo crear un bot dentro de Discord que utiliza la API de Open Cloud para almacenes de datos para eliminar datos de PII como una solución de automatización.
Flujo de trabajo
Al completar este tutorial, deberías ser capaz de crear un programa personalizado que se ejecute localmente y que automatice el manejo de solicitudes de derecho a la eliminación de los usuarios. El flujo de trabajo para este proceso es el siguiente:
- Roblox Support recibe una solicitud de derecho a la eliminación de un usuario.
- Se activa el webhook de Roblox, que contiene el ID de usuario y una lista de IDs de lugar inicial para los juegos a los que se han unido en la carga útil.
- Tu bot escucha estas notificaciones del webhook, verifica su autenticidad y utiliza la API de Open Cloud para almacenes de datos para eliminar los datos de PII almacenados en los almacenes de datos.
- El bot responde al mensaje del webhook en Discord con el estado de eliminación.

Configurar un webhook con integración de terceros
Antes de crear un bot, configura un servidor con integración de webhook en la aplicación de mensajería de terceros. Luego, usa el servidor para configurar un webhook en el Panel de Creadores.
Configurar un servidor
Los siguientes pasos muestran cómo configurar el servidor usando Discord.
Crea un nuevo servidor de Discord. Si no estás familiarizado con el proceso, consulta Soporte de Discord.
El servidor crea automáticamente un canal #general como tu canal predeterminado. Haz clic en el ícono de Editar Canal del canal #general.
Bajo Permisos, configura el canal como privado.
Crea una integración de webhook con el nuevo servidor, nómbrala como RTBF Hook. Si no estás familiarizado con el proceso, consulta Soporte de Discord.
Copia la URL del webhook y guárdala en un lugar seguro. Solo permite que miembros confiables de tu equipo tengan acceso a ella, ya que filtrar la URL puede permitir que actores maliciosos envíen mensajes falsos y potencialmente eliminen tus datos de usuario.
Configurar un webhook en Roblox
Después de obtener la URL del servidor de terceros, utilízala para configurar un webhook en el Panel de Creadores. asegúrate de que realices las siguientes configuraciones:
- Agrega la URL del servidor de Discord como la URL del Webhook.
- Incluye un Secreto personalizado. Aunque un secreto es opcional para completar la configuración, debes incluir uno para evitar que actores maliciosos se hagan pasar por Roblox y eliminen tus datos. Para más información sobre el uso de un secreto, consulta Verificar la seguridad del webhook.
- Selecciona Solicitud de Derecho a la Eliminación en Disparadores.
Puedes probar el webhook usando el botón Probar Respuesta para ver si recibes una notificación en el canal #general de tu servidor de Roblox. Si no recibes la notificación, intenta nuevamente o revisa la configuración de tu servidor para solucionar el error.
Configurar un bot
Después de que agregues el webhook, úsalos para configurar el bot con los siguientes pasos. Para más información, consulta la documentación de Discord.
- Navega a la página de Aplicaciones.
- Crea una nueva aplicación y nómbrala RTBF Bot.
- El sistema te redirige a la configuración de Información General del bot. Copia y guarda su ID de aplicación en un lugar seguro.
- En el menú de configuración, selecciona OAuth2.
- Navega a la configuración de OAuth2
- Habilita el ámbito bot, se mostrará una lista de permisos adicionales del Bot.
- Agrega el permiso Administrador. Guarda tus cambios.
- Guarda la URL generada.
- Copia la URL generada. Deja la página de configuración de la aplicación abierta.
- Navega a la URL generada. Selecciona el servidor de destino. Haz clic en el botón Continuar y luego en el botón Autorizar.
- Regresa a la página de configuración de la aplicación y navega a los ajustes del Bot.
- En la sección Intenciones de Gateway Privilegiadas, habilita Intención de Contenido de Mensaje.
- En la configuración del Bot > sección Construir un Bot, guarda el token del bot en un lugar seguro para pasos posteriores. Si no ves el token, haz clic en el botón Restablecer Token para generar uno nuevo.
Crear una clave de API de Open Cloud
Para permitir que tu bot de terceros acceda a tus almacenes de datos para almacenar datos de PII de los usuarios, crea una clave de API de Open Cloud que pueda acceder a tus juegos y añade el permiso Eliminar Entrada de los almacenes de datos para la eliminación de datos. Si utilizas almacenes de datos ordenados para almacenar PII, también necesitas agregar el permiso Escribir de los almacenes de datos ordenados. Al finalizar, copia y guarda la clave de API en un lugar seguro para usarla en pasos posteriores.
Obtener identificadores de juegos y lugares
Para que el bot localice los datos de PII solicitados por los usuarios para eliminación, obtén los siguientes identificadores de todos los juegos para los que pretendes usar el bot:
- El ID de Universo, el identificador único de tu juego.
- El ID de Lugar Inicial, el identificador único del lugar inicial de un juego.
Para obtener estos identificadores:
Navega al Panel de Creadores.
Pasa el cursor sobre la miniatura de un juego, haz clic en el botón ⋯ y selecciona Copiar ID de Universo y Copiar ID de Lugar Inicial, respectivamente.

Agregar scripts
Después de terminar de configurar el webhook, el bot y la clave de API para almacenes de datos, agrégalas a los scripts que implementan la lógica de automatización del bot. El siguiente ejemplo utiliza Python 3.
Instala las bibliotecas de Python usando los siguientes comandos:
Instalar Bibliotecaspip3 install discordpip3 install requestspip3 install urllib3==1.26.6Copia y guarda los siguientes scripts correspondientes a diferentes partes de la lógica del bot en el mismo directorio:
bot_config.pyBOT_TOKEN = ""OPEN_CLOUD_API_KEY = ""ROBLOX_WEBHOOK_SECRET = ""# Diccionario del ID de lugar inicial a# (ID de universo, lista de (nombre de almacenes de datos, ámbito y clave de entrada)) para# Almacenes de Datos Estándar# Los datos del usuario almacenados bajo estas entradas serán eliminadosSTANDARD_DATA_STORE_ENTRIES = {# ID de Lugar Inicial111111111: (# ID de Universo222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Diccionario del ID de lugar inicial a# (ID de universo, lista de (nombre de almacenes de datos, ámbito y clave de entrada)) para# Almacenes de Datos Ordenados# Los datos del usuario almacenados bajo estas entradas serán eliminadosORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""Llama a la API de Open Cloud de Almacenes de Datos para eliminar todas las entradas para un user_id configurado enSTANDARD_DATA_STORE_ENTRIES. Retorna una lista de eliminaciones exitosas y fracasadas."""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"""Llama a la API de Open Cloud de Almacenes de Datos Ordenados para eliminar todas las entradas para un user_id configurado enORDERED_DATA_STORE_ENTRIES. Retorna una lista de eliminaciones exitosas y fracasadas."""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"""Analiza el mensaje recibido en busca de la firma de Roblox y el timestamp, el pie solo se establece siconfiguraste el secreto del webhook"""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"""Verifica la firma de Roblox con el secreto configurado para comprobar su validez"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# Previene ataques de repetición dentro de una ventana de 300 segundosrequest_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# Valida la firmatimestamp_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# Firma válidareturn True"""Analiza un mensaje de webhook recibido en Discord. Extrae el ID de usuario, previene ataques de repeticiónbasados en el timestamp recibido y verifica la firma de Roblox con el secreto configurado para comprobar suvalidez."""def parse_message(message):# Analiza el mensaje recibido en busca de ID de usuario y ID de juegoif 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} está escuchando mensajes de Derecho a la Eliminación")"""Manejador para mensajes de webhook de Roblox"""@client.eventasync def on_message(message):# Analiza y valida el mensajeuser_id, start_place_ids = message_parser.parse_message(message)if not user_id or not start_place_ids:return# Elimina los datos de usuario de los almacenes de datos estándar[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Se eliminaron los datos de los almacenes de datos estándar para " +f"el ID de usuario: {user_id}, datos: {dict(successes)}")if failures:await message.reply(f"Falló al eliminar los datos de los almacenes de datos estándar para " +f"el ID de usuario: {user_id}, datos: {dict(failures)}")# Elimina los datos de usuario de los almacenes de datos ordenados[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Se eliminaron los datos de los almacenes de datos ordenados para " +f"el ID de usuario: {user_id}, datos: {dict(successes)}")if failures:await message.reply(f"Falló al eliminar los datos de los almacenes de datos ordenados para " +f"el ID de usuario: {user_id}, datos: {dict(failures)}")client.run(bot_config.BOT_TOKEN)if __name__ == "__main__":run()En el archivo bot_config.py para la configuración principal del bot:
- Establece BOT_TOKEN al token generado por tu bot.
- Establece OPEN_CLOUD_API_KEY como la clave API que creaste.
- Establece ROBLOX_WEBHOOK_SECRET como el secreto que configuraste al configurar el webhook en el Panel de Creadores.
- En los diccionarios STANDARD_DATA_STORE_ENTRIES y ORDERED_DATA_STORE_ENTRIES para localizar el almacén de datos de cada registro que se desea eliminar:
- Agrega tus IDs de Lugar Inicial copiados como claves.
- Agrega los IDs de Universo como el primer elemento de la tupla de valor.
- Reemplaza el segundo elemento de la tupla con el nombre, el ámbito, el nombre de clave de entrada y el ID de usuario asociado de tus almacenes de datos. Si usas un esquema de datos diferente, modifica para que coincida con tu propio esquema de datos.
Ejecuta el siguiente comando para ejecutar el bot:
Ejecutar Bot de Discordpython3 discord_bot.pyEl bot comenzará a escuchar y verificar los webhooks de Roblox para solicitudes de derecho a la eliminación y llamará al endpoint de Open Cloud para eliminar el correspondiente almacén de datos.
Probar
Puedes crear y enviar un mensaje de prueba para verificar que tu programa personalizado puede manejar correctamente las solicitudes de derecho a la eliminación y eliminar datos de PII:
Envía una solicitud HTTP POST a tu servidor de webhook de Discord con el siguiente cuerpo de solicitud:
Ejemplo de Solicitudcurl -X POST {serverUrl}-H 'Content-Type: application/json'-d '{"embeds":[{"title":"RightToErasureRequest","description":"You have received a new notification for Right to Erasure for the User Id: {userIds} in the game(s) with Ids: {gameIds}","footer":{"icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png","text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"}}]}'Si tienes un secreto de webhook:
- Genera un Roblox-Signature aplicando codificación HMAC-SHA256 a tu clave secreta de webhook.
- Establece el tiempo actual usando el timestamp UTC en segundos como Timestamp.
Reúne la description en el siguiente formato:
Formato del Campo de Descripción{Timestamp}. You have received a new notification for Right to Erasure for the User Id: {userId} in the game(s) with Ids: {gameIds}`.Por ejemplo:
Ejemplo de Campo de Descripción1683927229. You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955
Tu programa debería ser capaz de identificar que tu mensaje proviene de la fuente oficial de Roblox, ya que codificaste el mensaje con tu secreto. Debe entonces eliminar los datos de PII asociados a tu solicitud.
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}