Automatize o direito ao esquecimento usando webhooks

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

As 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 (frequentemente referido como "direito ao esquecimento" ou "direito de deletar"). Se você armazena qualquer dado pessoal ou Informação Pessoal Identificável (PII), como IDs de usuários, você é responsável por cumprir com o(s) quadro(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.

Considere alternativas

Para dados pessoais em armazenamentos de dados, a opção de automação mais fácil é configurar modelos de exclusão RTBF. Quando o Roblox processa uma solicitação RTBF, ele exclui automaticamente as entradas correspondentes de armazenamentos de dados padrão ou ordenados sem exigir que você hospede código personalizado. Para configurar modelos e revisar padrões suportados, veja Direito de ser esquecido (RTBF) em armazenamentos de dados.

O fluxo de trabalho do webhook nesta página é útil quando:

  • Você armazena dados pessoais fora de armazenamentos de dados.
  • O esquema do seu armazenamento de dados ou caso de uso não é suportado pelos modelos de exclusão RTBF.
  • Você precisa de lógica personalizada de processamento ou exclusão.

Fluxo de trabalho do webhook do criador

Este tutorial demonstra como configurar um webhook e criar um bot do Discord em execução localmente que usa a API Open Cloud para armazenamentos de dados para automatizar o manuseio de solicitações de direito ao esquecimento. O fluxo de trabalho para este processo é o seguinte:

  1. O Suporte do Roblox recebe uma solicitação de direito ao esquecimento de um usuário.
  2. O webhook do Roblox é acionado, contendo o ID do usuário e uma lista de IDs de Locais Iniciais para os jogos que eles se juntaram na carga útil.
  3. Seu bot escuta essas notificações de webhook, verifica sua autenticidade e utiliza a API Open Cloud para armazenamentos de dados para excluir os dados PII armazenados nos armazenamentos de dados.
  4. O bot responde à mensagem do 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 do Criador.

Configurar um servidor

As seguintes etapas mostram como configurar o servidor usando o Discord.

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

  2. O servidor cria automaticamente um canal #geral como seu canal padrão. Clique no ícone 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, nomeando-a como RTBF Hook. Se você não estiver 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 confiáveis da equipe acessem, 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 do Criador. Certifique-se de realizar as seguintes configurações:

  • Adicione a URL do servidor Discord como a Webhook URL.
  • Inclua um Segredo personalizado. Embora um segredo seja opcional para concluir 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 ao Esquecimento em Gatilhos.

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 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 com as seguintes etapas. 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 salve 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. Ative o escopo bot, uma lista de Permissões Adicionais do Bot será exibida.
    2. Adicione a permissão Administrador. Salve suas alterações.
    3. Salve a URL gerada.
  6. Copie a URL gerada. Deixe a página de configurações da aplicação aberta.
  7. Navegue até a URL gerada. Selecione o servidor de destino. Clique no botão Continuar e depois no botão Autorizar.
  8. Navegue de volta 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, ative 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 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 armazenamentos de dados para armazenar dados PII dos usuários, crie uma chave da API Open Cloud que possa acessar seus jogos e adicione a permissão Excluir Entrada dos armazenamentos de dados para exclusão de dados. Se você usar armazenamentos de dados ordenados para armazenar PII, também precisará adicionar a permissão Escrever dos armazenamentos de dados ordenados. Após a conclusão, copie e salve a chave da API em um local seguro para usá-la nas etapas posteriores.

Obter identificadores de jogos e locais

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 único do seu jogo.
  • O ID do Local Inicial, o identificador único do local inicial de um jogo.

Para obter esses identificadores:

  1. Navegue até o Painel do Criador.

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

Adicionar scripts

Depois de terminar de configurar o webhook, o bot e a chave da API para armazenamentos 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 do 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 do ID do Local Inicial para
    # (ID do universo, lista de (nome dos armazenamentos de dados, escopo e chave de entrada)) para
    # Armazenamentos de Dados Padrão
    # Os dados do usuário armazenados sob essas entradas serão excluídos
    STANDARD_DATA_STORE_ENTRIES = {
    # ID do Local 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 do ID do Local Inicial para
    # (ID do universo, lista de (nome dos armazenamentos de dados, escopo e chave de entrada)) para
    # Armazenamentos 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 de Armazenamentos de Dados para excluir todas as entradas para um user_id configurado em
    STANDARD_DATA_STORE_ENTRIES. Retorna uma lista de exclusões bem-sucedidas e falhas ao excluir.
    """
    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 de Armazenamentos de Dados Ordenados para excluir todas as entradas para um user_id configurado em
    ORDERED_DATA_STORE_ENTRIES. Retorna uma lista de exclusões bem-sucedidas e falhas ao excluir.
    """
    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 para a assinatura e timestamp do Roblox, o rodapé só é definido se você
    configurou o segredo do 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 de uma janela 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 para 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"Você recebeu uma nova notificação para o Direito ao Esquecimento para o ID do Usuário: (.*) no(s) jogo(s) com 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 ao Esquecimento")
    """
    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
    # Exclui dados do usuário de armazenamentos de dados padrão
    [successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Dados de armazenamentos de dados padrão excluídos para " +
    f"ID do usuário: {user_id}, dados: {dict(successes)}")
    if failures:
    await message.reply(f"Falha ao excluir dados de armazenamentos de dados padrão para " +
    f"ID do usuário: {user_id}, dados: {dict(failures)}")
    # Exclui dados do usuário de armazenamentos de dados ordenados
    [successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Dados de armazenamentos de dados ordenados excluídos para " +
    f"ID do usuário: {user_id}, dados: {dict(successes)}")
    if failures:
    await message.reply(f"Falha ao excluir dados de armazenamentos 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ê definiu ao configurar o webhook no Painel do Criador.
    4. Nos dicionários STANDARD_DATA_STORE_ENTRIES e ORDERED_DATA_STORE_ENTRIES para localizar o armazenamento de dados de cada registro a ser excluído:
      1. Adicione seus IDs de Locais Iniciais copiados como chaves.
      2. Adicione IDs de Universos como o primeiro elemento do valor da tupla.
      3. Substitua o segundo elemento da tupla pelo nome, escopo, nome da chave de entrada e ID de usuário associado dos seus armazenamentos de dados. Se você usar um esquema de dados diferente, modifique para corresponder ao seu próprio esquema de dados.
  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 ao esquecimento e chama o endpoint Open Cloud para excluir o armazenamento 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 ao esquecimento e excluir dados PII:

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

    Exemplo de Solicitação
    curl -X POST {serverUrl}
    -H 'Content-Type: application/json'
    -d '{
    "embeds":[{
    "title":"RightToErasureRequest",
    "description":"Você recebeu uma nova notificação para o Direito ao Esquecimento 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 à sua chave secreta do 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 o Direito ao Esquecimento 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 o Direito ao Esquecimento 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 excluir os dados PII associados à sua solicitação.

Exemplo de Corpo
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "Você recebeu uma nova notificação para o Direito ao Esquecimento 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.