El Reglamento general de protección de datos (RGDP) es un reglamento europeo sobre protección de datos y privacidad.Otorga a las personas el derecho de solicitar la eliminación de sus datos personales, conocido como el derecho de supresión.Si almacenas cualquier Información personalmente identificable (Información de Identificación Personal (IIP)) de tus usuarios, como sus ID de usuario, debes cumplir con los requisitos de GDPR al eliminar esta información al recibir la solicitud de un usuario.
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 tiendas de datos siendo la forma más común de almacenar datos PII, este tutorial proporciona un ejemplo de cómo crear un bot dentro de Guilded o Discord que use la API de nube abierta para tiendas de datos para eliminar datos PII como solución de automatización.
Flujo de trabajo
Una vez que completes este tutorial, deberías poder crear un programa personalizado que se ejecute localmente que automatice el manejo de solicitudes de derecho de borrado de usuarios.El flujo de trabajo para este proceso es el siguiente:
- El soporte de Roblox recibe una solicitud de borrado de un usuario.
- Se activa el webhook de Roblox, que contiene el ID de usuario y una lista de ID de lugar de inicio para las experiencias a las que se han unido en la carga útil.
- Tu bot escucha estas notificaciones de webhook, verifica su autenticidad y utiliza la API de nube abierta para almacenar datos para eliminar los datos PII almacenados en los almacenes de datos.
- El bot responde al mensaje de webhook en Discord o Guilded 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 webhooks en la aplicación de mensajería de terceros.Luego use el servidor para configurar un webhook en el tablero de Creator.
Configurar un servidor
Los siguientes pasos muestran cómo configurar el servidor usando Guilded o Discord.
- Crea un nuevo servidor de Guilded. Si no estás familiarizado con el proceso, consulta Soporte de Guilded.
- Bajo las configuraciones de Privacidad , establece el servidor en servidor privado.El servidor crea automáticamente un canal privado #general como canal predeterminado.
- Crea una integración de webhook con el nuevo servidor y dale un nombre que puedas entender fácilmente, como GDPR Hook.Si no está familiarizado con el proceso, consulte Soporte de Guilded.
- Copia la URL del webhook y guárdala en un lugar seguro.Solo permite que los miembros del equipo de confianza accedan a él, ya que filtrar la URL puede permitir que los actores malos envíen mensajes falsos y potencialmente eliminen los datos de tu usuario.
Configurar un webhook en Roblox
Después de obtener la URL del servidor de terceros, úsela para configurar un webhook en el tablero de Creator.asegúrese de realizar las siguientes configuraciones:
- Añade la URL del servidor Guilded o Discord como la URL de Webhook .
- Incluye un secreto personalizado Secreto .Aunque un secreto es opcional para completar la configuración, deberías incluir uno para evitar que los actores malos se hagan pasar por Roblox y eliminen tus datos.Para obtener más información sobre el uso de un secreto, vea Verificar la seguridad del punto final web.
- Seleccione Derecho a solicitar la eliminación debajo de Gatillos .
Puedes probar el webhook usando el botón Respuesta de prueba para ver si recibes una notificación en el canal #general de tu servidor de Roblox.Si no recibe la notificaciones, intente de nuevo o verifique la configuración de su servidor para solucionar el error.

Configurar un bot
Después de agregar el webhook, úselo para configurar el bot con los siguientes pasos:
Abre la lista de todos los servidores haciendo clic en su icono o usando el atajo:
- CtrlS en Windows.
- ⌘S en Mac.
Seleccione su servidor para recibir notificaciones de derecho a la eliminación.
Expanda la lista debajo de Inicio del servidor y seleccione Administrar bots .
El servidor crea automáticamente un canal privado #general como canal predeterminado.
Haga clic en el botón Crear un bot y agregue un nombre de bot. Guilded lo redirige a la página de configuración del bot.
Seleccione la sección API en la página de configuración del bot.
Bajo la sección Fichas , haga clic en el botón Generar ficha .
Guarde y almacene el token generado en un lugar seguro.
Crear una clave de API de nube abierta
Para permitir que tu bot de terceros acceda a tus almacenes de datos para almacenar datos PII de usuarios, crea una clave de API de nube abierta que pueda acceder a tus experiencias y agrega el permiso Eliminar entrada de almacenamiento de datos para la eliminación de datos.Si usa almacenamientos de datos ordenados para almacenar Información de Identificación Personal (IIP), también debe agregar el permiso Escribir de almacenamientos de datos ordenados.Una vez completado, copia y guarda la clave de la API en una ubicación segura para usarla en pasos posteriores.
Obtener identificadores de experiencias y lugares
Para que el bot localice los datos de PII solicitados por los usuarios para su eliminación, obtenga los siguientes identificadores de todas las experiencias que tiene previsto utilizar el bot:
- El ID del universo , el identificador único de tu experiencia.
- El ID de lugar de inicio , el identificador único del lugar de inicio de una experiencia.
Para obtener estos identificadores:
Navegue hasta el Panel del creador.
Pase el cursor sobre una miniatura de experiencia, haga clic en el botón ⋯ y seleccione Copiar ID del universo y Copiar ID del lugar de inicio , respectivamente.
Añadir scripts
Después de terminar de configurar el webhook, el bot y la clave de API para almacenamientos de datos, agréguelos a los scripts que implementan la lógica de automatización del bot.El siguiente ejemplo usa Python 3:
Instale bibliotecas de Python usando los siguientes comandos:
Instalar bibliotecaspip3 install guilded.py==1.8.0pip3 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 = ""# Dictamen de la ID del lugar de inicio a# (ID del universo, lista de (nombre de almacén de datos, alcance y clave de entrada)) para# Almacenes de datos estándar# Los datos del usuario almacenados bajo estas entradas se eliminaránSTANDARD_DATA_STORE_ENTRIES = {# ID de lugar de inicio111111111: (# ID del universo222222222,[("StandardDataStore1", "Scope1", "Key1_{user_id}"),("StandardDataStore1", "Scope1", "Key2_{user_id}"),("StandardDataStore2", "Scope1", "Key3_{user_id}")]),33333333: (444444444,[("StandardDataStore3", "Scope1", "Key1_{user_id}")])}# Dictamen de la ID del lugar de inicio a# (ID del universo, lista de (nombre de almacén de datos, alcance y clave de entrada)) para# Almacenes de datos ordenados# Los datos del usuario almacenados bajo estas entradas se eliminaránORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""Calls Data Stores Open Cloud API to delete all entries for a user_id configured inSTANDARD_DATA_STORE_ENTRIES. Returns a list of successful deletions and failures to delete."""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"""Calls Ordered Data Stores Open Cloud API to delete all entries for a user_id configured inORDERED_DATA_STORE_ENTRIES. Returns a list of successful deletions and failures to delete."""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"""Parses received message for Roblox signature and timestamp, the footer is only set if youconfigured webhook 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"""Verifies Roblox signature with configured secret to check for validity"""def validate_signature(message, signature, timestamp):if not message or not signature or not timestamp:return False# Previene el ataque de repetición dentro de la 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"""Parses a received webhook messaged on Discord or Guilded. Extracts user ID, prevents replay attackbased on timestamp received, and verifies Roblox signature with configured secret to check forvalidity."""def parse_message(message):# Parsa recibe mensaje por ID de usuario e ID del 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 "", []guilded_bot.pyimport guildedimport jsonimport bot_configimport data_stores_apiimport message_parserdef run():client = guilded.Client()@client.eventasync def on_ready():print(f"{client.user} is listening to Right to Erasure messages")"""Handler for webhook messages from Roblox"""@client.eventasync def on_message(message):# Para analizar y validar 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 almacenes de datos estándar del usuario[successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Deleted standard data stores data for " +f"user ID: {user_id}, data: {dict(successes)}")if failures:await message.reply(f"Failed to delete standard data stores data for " +f"user ID: {user_id}, data: {dict(failures)}")# Elimina los datos del almacén de datos solicitados del usuario[successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)if successes:await message.reply(f"Deleted ordered data stores data for " +f"user ID: {user_id}, data: {dict(successes)}")if failures:await message.reply(f"Failed to delete ordered data stores data for " +f"user ID: {user_id}, data: {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 de API que creaste.
- Establece ROBLOX_WEBHOOK_SECRET como el secreto que configuraste al configurar el webhook en el tablero de Creator.
- En STANDARD_DATA_STORE_ENTRIES y ORDERED_DATA_STORE_ENTRIES diccionarios para localizar el almacén de datos de cada registro para eliminar:
- Agrega tus ID de lugar de inicio copiados como claves.
- Añade ID de universo como el primer elemento del valor de la tupla.
- Reemplaza el segundo elemento de la tupla con el nombre, el alcance, el nombre de la clave de entrada y el ID de usuario asociado de tus almacenes de datos.Si usa un esquema de datos diferente, modifíquelo para que coincida con su propio esquema de datos en consecuencia.
Ejecute el siguiente comando para ejecutar el bot:
Ejecutar el bot de Guildedpython3 guilded_bot.pyEl bot luego comienza a escuchar y verificar los webhooks de Roblox para solicitudes de derecho de eliminación y llama al punto final de nube tiendapara eliminar el almacén de datos correspondiente.
Prueba
Puedes crear y ejecutar un mensaje de prueba para verificar que tu programa personalizado pueda manejar correctamente las solicitudes de borrado y eliminar los datos de PII:
Envía una solicitud HTTP POST a tu servidor webhook de Guilded o Discord con el siguiente cuerpo de solicitud:
Petición de ejemplocurl -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 el código de HMAC-SHA256 a la clave secreta de tu webhook.
- Establece el tiempo actual usando el marcador de tiempo UTC en segundos como Timestamp .
Poner juntos el description en el siguiente formato:
Description Field Format{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:
Example Description Field1683927229. You have received a new notification for Right to Erasure for the User Id: 2425654247 in the game(s) with Ids: 10539205763, 13260950955
Su programa debería ser capaz de identificar que su mensaje proviene de la fuente oficial de Roblox ya que codificó el mensaje con su secreto.Debe eliminar entonces los datos de PII asociados con su solicitud.
Cuerpo de ejemplo
{
"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://crear.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}