Automatizar solicitações de direito à exclusão

*Este conteúdo é traduzido por IA (Beta) e pode conter erros. Para ver a página em inglês, clique aqui.

Regulamentações globais de proteção de dados e privacidade concedem aos indivíduos o direito de controlar seus dados, incluindo o direito de solicitar sua exclusão (geralmente referido como o "direito à exclusão" ou "direito de deletar"). Se você armazenar quaisquer dados pessoais ou Informações Pessoais Identificáveis (PII), como IDs de Usuário, você é responsável por cumprir com o(s) framework(s) de privacidade aplicáveis, excluindo permanentemente essas informações ao receber uma solicitação do usuário. Mais informações podem ser encontradas em RTBF e criadores.

Em vez de lidar com solicitações manualmente, você pode configurar um webhook e usar um bot dentro de um aplicativo de mensagens de terceiros para automatizar o processo. Como repositórios de dados são a forma mais comum de armazenar dados PII, este tutorial fornece um exemplo de como criar um bot no Discord que usa a API Open Cloud para repositórios de dados para deletar dados PII como uma solução de automação.

Fluxo de trabalho

Após concluir este tutorial, você deve ser capaz de criar um programa personalizado que roda localmente e automatiza o manuseio de solicitações de direito à exclusão de usuários. O fluxo de trabalho desse processo é o seguinte:

  1. O Suporte do Roblox recebe uma solicitação de direito à exclusão de um usuário.
  2. O webhook do Roblox é acionado, contendo o ID do Usuário e uma lista de IDs de Lugar Inicial para os jogos que o usuário se juntou no payload.
  3. Seu bot escuta essas notificações de webhook, verifica sua autenticidade e utiliza a API Open Cloud para repositórios de dados para deletar os dados PII armazenados em repositórios de dados.
  4. O bot responde à mensagem de webhook no Discord com o status da exclusão.

Configurar um webhook com integração de terceiros

Antes de criar um bot, configure um servidor com integração de webhook no aplicativo de mensagens de terceiros. Em seguida, use o servidor para configurar um webhook no Painel de Criadores.

Configurar um servidor

Os seguintes passos mostram como configurar o servidor usando o Discord.

  1. Crie um novo servidor no Discord. Se você não está familiarizado com o processo, veja Suporte do Discord.

  2. O servidor cria automaticamente um canal #geral como seu canal padrão. Clique no ícone de Editar Canal do canal #geral.

  3. Em Permissões, defina o canal como privado.

  4. Crie uma integração de webhook com o novo servidor, nomeie-a como RTBF Hook. Se você não está familiarizado com o processo, veja Suporte do Discord.

  5. Copie a URL do webhook e armazene-a em um local seguro. Permita apenas que membros da equipe confiáveis tenham acesso a ela, pois vazar a URL pode permitir que agentes mal-intencionados enviem mensagens falsas e potencialmente excluam seus dados de usuário.

Configurar um webhook no Roblox

Após obter a URL do servidor de terceiros, use-a para configurar um webhook no Painel de Criadores. Certifique-se de realizar as seguintes configurações:

  • Adicione a URL do servidor Discord como a URL do Webhook.
  • Inclua um Segredo personalizado. Embora um segredo seja opcional para completar a configuração, você deve incluir um para evitar que agentes mal-intencionados se façam passar pelo Roblox e excluam seus dados. Para mais informações sobre o uso de um segredo, veja Verificar segurança do webhook.
  • Selecione Solicitação de Direito à Exclusão em Acionadores.

Você pode testar o webhook usando o botão Testar Resposta para ver se você recebe uma notificação no canal #geral do seu servidor a partir do Roblox. Se você não receber a notificação, tente novamente ou verifique as configurações do seu servidor para solucionar o erro.

Configurar um bot

Depois de adicionar o webhook, use-o para configurar o bot seguindo os passos a seguir. Para mais informações, veja a documentação do Discord.

  1. Navegue até a página de Aplicações.
  2. Crie uma nova aplicação e nomeie-a como RTBF Bot.
  3. O sistema redireciona você para as configurações de Informações Gerais do bot. Copie e armazene seu ID de aplicação em um local seguro.
  4. No menu de configurações, selecione OAuth2.
  5. Navegue até as configurações de OAuth2
    1. Habilite o escopo bot, uma lista de Permissões de Bot adicionais será exibida.
    2. Adicione a permissão Administrador. Salve suas alterações.
    3. Salve a URL gerada.
  6. Copie a URL gerada. Não feche a página de configurações da aplicação.
  7. Navegue até a URL gerada. Selecione o servidor alvo. Clique no botão Continuar e, em seguida, no botão Autorizar.
  8. Volte para a página de configurações da aplicação e vá para as configurações do Bot.
  9. Na seção Intenções de Gateway Privilegiadas, habilite Intenção de Conteúdo da Mensagem.
  10. Nas configurações do Bot > seção Construir um Bot, salve o token do bot em um local seguro para as etapas posteriores. Se você não ver o token, clique no botão Redefinir Token para gerar um novo.

Criar uma chave da API Open Cloud

Para permitir que seu bot de terceiros acesse seus repositórios de dados para armazenar dados PII dos usuários, crie uma chave da API Open Cloud que pode acessar seus jogos e adicionar a permissão Deletar Entrada dos repositórios de dados para exclusão de dados. Se você usar repositórios de dados ordenados para armazenar PII, você também precisará adicionar a permissão Escrever dos repositórios de dados ordenados. Após a conclusão, copie e armazene a chave da API em um local seguro para usá-la nas etapas posteriores.

Obter identificadores de jogos e lugares

Para que o bot localize os dados PII solicitados pelos usuários para exclusão, obtenha os seguintes identificadores de todos os jogos que você pretende usar o bot:

  • O ID do Universo, o identificador exclusivo do seu jogo.
  • O ID do Lugar Inicial, o identificador exclusivo do lugar inicial de um jogo.

Para obter esses identificadores:

  1. Navegue até o Painel de Criadores.

  2. Passe o mouse sobre a miniatura de um jogo, clique no botão e selecione Copiar ID do Universo e Copiar ID do Lugar Inicial, respectivamente.

Adicionar scripts

Depois que você terminar de configurar o webhook, o bot e a chave da API para repositórios de dados, adicione-os aos scripts que implementam a lógica de automação do bot. O seguinte exemplo usa Python 3.

  1. Instale as bibliotecas Python usando os seguintes comandos:

    Instalar Bibliotecas
    pip3 install discord
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copie e salve os seguintes scripts correspondentes a diferentes partes da lógica do bot no mesmo diretório:

    bot_config.py
    BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Dicionário de ID do Lugar Inicial para
    # (ID do universo, lista de (nome dos repositórios de dados, escopo e chave de entrada)) para
    # Repositórios de Dados Padrão
    # Os dados do usuário armazenados sob essas entradas serão excluídos
    STANDARD_DATA_STORE_ENTRIES = {
    # ID do Lugar Inicial
    111111111: (
    # ID do universo
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # Dicionário de ID do Lugar Inicial para
    # (ID do universo, lista de (nome dos repositórios de dados, escopo e chave de entrada)) para
    # Repositórios de Dados Ordenados
    # Os dados do usuário armazenados sob essas entradas serão excluídos
    ORDERED_DATA_STORE_ENTRIES = {
    111111111: (
    222222222,
    [
    ("OrderedDataStore1", "Scope2", "Key4_{user_id}")
    ]
    )
    }
    data_stores_api.py
    import requests
    import bot_config
    from collections import defaultdict
    """
    Chama a API Open Cloud dos Repositórios de Dados para deletar todas as entradas para um user_id configurado em
    STANDARD_DATA_STORE_ENTRIES. Retorna uma lista de exclusões bem-sucedidas e falhas ao deletar.
    """
    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
    """
    Chama a API Open Cloud dos Repositórios de Dados Ordenados para deletar todas as entradas para um user_id configurado em
    ORDERED_DATA_STORE_ENTRIES. Retorna uma lista de exclusões bem-sucedidas e falhas ao deletar.
    """
    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
    """
    Analisa a mensagem recebida em busca de assinatura e timestamp do Roblox, o rodapé só é definido se você
    configurou um segredo de 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 a assinatura do Roblox com o segredo configurado para checar a validade
    """
    def validate_signature(message, signature, timestamp):
    if not message or not signature or not timestamp:
    return False
    # Previne ataque de repetição dentro do intervalo 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 a assinatura
    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
    # Assinatura válida
    return True
    """
    Analisa uma mensagem de webhook recebida no Discord. Extrai o ID do usuário, previne ataque de repetição
    com base no timestamp recebido e verifica a assinatura do Roblox com o segredo configurado para checar a validade.
    """
    def parse_message(message):
    # Analisa a mensagem recebida em busca do ID do usuário e ID do jogo
    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á ouvindo mensagens de Direito à Exclusão")
    """
    Manipulador para mensagens de webhook do Roblox
    """
    @client.event
    async def on_message(message):
    # Analisa e valida a mensagem
    user_id, start_place_ids = message_parser.parse_message(message)
    if not user_id or not start_place_ids:
    return
    # Deleta dados do usuário dos repositórios de dados padrão
    [successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Deletados dados de repositórios de dados padrão para " +
    f"ID do usuário: {user_id}, dados: {dict(successes)}")
    if failures:
    await message.reply(f"Falha ao deletar dados de repositórios de dados padrão para " +
    f"ID do usuário: {user_id}, dados: {dict(failures)}")
    # Deleta dados do usuário dos repositórios de dados ordenados
    [successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Deletados dados de repositórios de dados ordenados para " +
    f"ID do usuário: {user_id}, dados: {dict(successes)}")
    if failures:
    await message.reply(f"Falha ao deletar dados de repositórios de dados ordenados para " +
    f"ID do usuário: {user_id}, dados: {dict(failures)}")
    client.run(bot_config.BOT_TOKEN)
    if __name__ == "__main__":
    run()
  3. No arquivo bot_config.py para configuração principal do bot:

    1. Defina BOT_TOKEN como o token gerado pelo seu bot.
    2. Defina OPEN_CLOUD_API_KEY como a chave da API que você criou.
    3. Defina ROBLOX_WEBHOOK_SECRET como o segredo que você configurou ao configurar o webhook no Painel de Criadores.
    4. Nos dicionários STANDARD_DATA_STORE_ENTRIES e ORDERED_DATA_STORE_ENTRIES para localizar o repositório de dados de cada registro a ser excluído:
      1. Adicione seus IDs de Lugar Inicial copiados como chaves.
      2. Adicione os IDs do Universo como o primeiro elemento da tupla de valor.
      3. Substitua o segundo elemento da tupla pelo nome, escopo, nome da chave de entrada e ID de Usuário associado dos seus repositórios de dados. Se você usar um esquema de dados diferente, modifique para corresponder ao seu próprio esquema de dados de acordo.
  4. Execute o seguinte comando para rodar o bot:

    Executar Bot do Discord
    python3 discord_bot.py
  5. O bot então começa a ouvir e verificar webhooks do Roblox para solicitações de direito à exclusão e chama o endpoint do Open Cloud para deletar o repositório de dados correspondente.

Testar

Você pode criar e executar uma mensagem de teste para verificar se seu programa personalizado pode lidar adequadamente com solicitações de direito à exclusão e deletar dados PII:

  1. Envie uma requisição HTTP POST para seu servidor de webhook Discord com o seguinte corpo de requisição:

    Exemplo de Requisição
    curl -X POST {serverUrl}
    -H 'Content-Type: application/json'
    -d '{
    "embeds":[{
    "title":"RightToErasureRequest",
    "description":"Você recebeu uma nova notificação para Direito à Exclusão para o ID do Usuário: {userIds} no(s) jogo(s) com IDs: {gameIds}",
    "footer":{
    "icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
    "text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"
    }
    }]
    }'
  2. Se você tiver um segredo de webhook:

    1. Gere uma Roblox-Signature aplicando a codificação HMAC-SHA256 na chave secreta de webhook.
    2. Defina a hora atual usando o timestamp UTC em segundos como Timestamp.
  3. Monte a description no seguinte formato:

    Formato do Campo de Descrição
    {Timestamp}. Você recebeu uma nova notificação para Direito à Exclusão para o ID do Usuário: {userId} no(s) jogo(s) com IDs: {gameIds}.

    Por exemplo:

    Exemplo de Campo de Descrição
    1683927229. Você recebeu uma nova notificação para Direito à Exclusão para o ID do Usuário: 2425654247 no(s) jogo(s) com IDs: 10539205763, 13260950955

Seu programa deve ser capaz de identificar que sua mensagem é da fonte oficial do Roblox, uma vez que você codificou a mensagem com seu segredo. Ele deve então deletar os dados PII associados à sua solicitação.

Exemplo de Corpo
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "Você recebeu uma nova notificação para Direito à Exclusão para o ID do Usuário: 2425654247 no(s) jogo(s) com 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, o logotipo Roblox e Powering Imagination estão entre nossas marcas registradas e não registradas nos EUA e em outros países.