Automatizar solicitudes de derecho a la eliminación

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

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:

  1. Roblox Support recibe una solicitud de derecho a la eliminación de un usuario.
  2. 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.
  3. 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.
  4. 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.

  1. Crea un nuevo servidor de Discord. Si no estás familiarizado con el proceso, consulta Soporte de Discord.

  2. El servidor crea automáticamente un canal #general como tu canal predeterminado. Haz clic en el ícono de Editar Canal del canal #general.

  3. Bajo Permisos, configura el canal como privado.

  4. 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.

  5. 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.

  1. Crea una nueva aplicación y nómbrala RTBF Bot.
  2. 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.
  3. En el menú de configuración, selecciona OAuth2.
  4. Navega a la configuración de OAuth2
    1. Habilita el ámbito bot, se mostrará una lista de permisos adicionales del Bot.
    2. Agrega el permiso Administrador. Guarda tus cambios.
    3. Guarda la URL generada.
  5. Copia la URL generada. Deja la página de configuración de la aplicación abierta.
  6. Navega a la URL generada. Selecciona el servidor de destino. Haz clic en el botón Continuar y luego en el botón Autorizar.
  7. Regresa a la página de configuración de la aplicación y navega a los ajustes del Bot.
  8. En la sección Intenciones de Gateway Privilegiadas, habilita Intención de Contenido de Mensaje.
  9. 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:

  1. Navega al Panel de Creadores.

  2. 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.

  1. Instala las bibliotecas de Python usando los siguientes comandos:

    Instalar Bibliotecas
    pip3 install discord
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copia y guarda los siguientes scripts correspondientes a diferentes partes de la lógica del bot en el mismo directorio:

    bot_config.py
    BOT_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 eliminados
    STANDARD_DATA_STORE_ENTRIES = {
    # ID de Lugar Inicial
    111111111: (
    # ID de Universo
    222222222,
    [
    ("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 eliminados
    ORDERED_DATA_STORE_ENTRIES = {
    111111111: (
    222222222,
    [
    ("OrderedDataStore1", "Scope2", "Key4_{user_id}")
    ]
    )
    }
    data_stores_api.py
    import requests
    import bot_config
    from collections import defaultdict
    """
    Llama a la API de Open Cloud de Almacenes de Datos para eliminar todas las entradas para un user_id configurado en
    STANDARD_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:
    continue
    universe_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 en
    ORDERED_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:
    continue
    universe_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, failures
    message_parser.py
    import time
    import hmac
    import hashlib
    import re
    import base64
    import bot_config
    """
    Analiza el mensaje recibido en busca de la firma de Roblox y el timestamp, el pie solo se establece si
    configuraste el secreto del webhook
    """
    def parse_footer(message):
    if not message.embeds[0].footer or \
    not message.embeds[0].footer.text:
    return "", 0
    footer_match = re.match(
    r"Roblox-Signature: (.*), Timestamp: (.*)",
    message.embeds[0].footer.text
    )
    if not footer_match:
    return "", 0
    else:
    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 segundos
    request_timestamp_ms = timestamp * 1000
    window_time_ms = 300 * 1000
    oldest_timestamp_allowed = round(time.time() * 1000) - window_time_ms
    if request_timestamp_ms < oldest_timestamp_allowed:
    return False
    # Valida la firma
    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
    # Firma válida
    return True
    """
    Analiza un mensaje de webhook recibido en Discord. Extrae el ID de usuario, previene ataques de repetición
    basados en el timestamp recibido y verifica la firma de Roblox con el secreto configurado para comprobar su
    validez.
    """
    def parse_message(message):
    # Analiza el mensaje recibido en busca de ID de usuario y ID de juego
    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_ids
    else:
    return "", []
    discord_bot.py
    import discord
    import bot_config
    import data_stores_api
    import message_parser
    def run():
    intents = discord.Intents.default()
    intents.message_content = True
    client = discord.Client(intents=intents)
    @client.event
    async def on_ready():
    print(f"{client.user} está escuchando mensajes de Derecho a la Eliminación")
    """
    Manejador para mensajes de webhook de Roblox
    """
    @client.event
    async def on_message(message):
    # Analiza y valida el mensaje
    user_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()
  3. En el archivo bot_config.py para la configuración principal del bot:

    1. Establece BOT_TOKEN al token generado por tu bot.
    2. Establece OPEN_CLOUD_API_KEY como la clave API que creaste.
    3. Establece ROBLOX_WEBHOOK_SECRET como el secreto que configuraste al configurar el webhook en el Panel de Creadores.
    4. 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:
      1. Agrega tus IDs de Lugar Inicial copiados como claves.
      2. Agrega los IDs de Universo como el primer elemento de la tupla de valor.
      3. 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.
  4. Ejecuta el siguiente comando para ejecutar el bot:

    Ejecutar Bot de Discord
    python3 discord_bot.py
  5. El 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:

  1. Envía una solicitud HTTP POST a tu servidor de webhook de Discord con el siguiente cuerpo de solicitud:

    Ejemplo de Solicitud
    curl -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}"
    }
    }]
    }'
  2. Si tienes un secreto de webhook:

    1. Genera un Roblox-Signature aplicando codificación HMAC-SHA256 a tu clave secreta de webhook.
    2. Establece el tiempo actual usando el timestamp UTC en segundos como Timestamp.
  3. 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ón
    1683927229. 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.

Ejemplo de Cuerpo
{
"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"
}
}
]
}
©2026 Roblox Corporation. Roblox, el logotipo de Roblox y "Powering Imagination" son algunas de nuestras marcas registradas y no registradas en los Estados Unidos y otros países.