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:
- O Suporte do Roblox recebe uma solicitação de direito ao esquecimento de um usuário.
- 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.
- 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.
- 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.
Crie um novo servidor no Discord. Se você não estiver familiarizado com o processo, veja Suporte do Discord.
O servidor cria automaticamente um canal #geral como seu canal padrão. Clique no ícone Editar Canal do canal #geral.
Em Permissões, defina o canal como privado.
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.
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.
- Navegue até a página de Aplicações.
- Crie uma nova aplicação e nomeie-a como RTBF Bot.
- 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.
- No menu de configurações, selecione OAuth2.
- Navegue até as configurações de OAuth2
- Ative o escopo bot, uma lista de Permissões Adicionais do Bot será exibida.
- Adicione a permissão Administrador. Salve suas alterações.
- Salve a URL gerada.
- Copie a URL gerada. Deixe a página de configurações da aplicação aberta.
- Navegue até a URL gerada. Selecione o servidor de destino. Clique no botão Continuar e depois no botão Autorizar.
- Navegue de volta para a página de configurações da aplicação e vá para as configurações do Bot.
- Na seção Intenções de Gateway Privilegiadas, ative Intenção de Conteúdo da Mensagem.
- 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:
Navegue até o Painel do Criador.
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.
Instale as bibliotecas do Python usando os seguintes comandos:
Instalar Bibliotecaspip3 install discordpip3 install requestspip3 install urllib3==1.26.6Copie e salve os seguintes scripts correspondentes a diferentes partes da lógica do bot no mesmo diretório:
bot_config.pyBOT_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ídosSTANDARD_DATA_STORE_ENTRIES = {# ID do Local Inicial111111111: (# ID do Universo222222222,[("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ídosORDERED_DATA_STORE_ENTRIES = {111111111: (222222222,[("OrderedDataStore1", "Scope2", "Key4_{user_id}")])}data_stores_api.pyimport requestsimport bot_configfrom collections import defaultdict"""Chama a API Open Cloud de Armazenamentos de Dados para excluir todas as entradas para um user_id configurado emSTANDARD_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: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"""Chama a API Open Cloud de Armazenamentos de Dados Ordenados para excluir todas as entradas para um user_id configurado emORDERED_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: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"""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 "", 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 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 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 a assinaturatimestamp_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álidareturn True"""Analisa uma mensagem de webhook recebida no Discord. Extrai o ID do usuário, previne ataque de repetiçãocom 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 jogoif 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_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á ouvindo mensagens de Direito ao Esquecimento")"""Manipulador para mensagens de webhook do Roblox"""@client.eventasync def on_message(message):# Analisa e valida a mensagemuser_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()No arquivo bot_config.py para configuração principal do bot:
- Defina BOT_TOKEN como o token gerado pelo seu bot.
- Defina OPEN_CLOUD_API_KEY como a chave da API que você criou.
- Defina ROBLOX_WEBHOOK_SECRET como o segredo que você definiu ao configurar o webhook no Painel do Criador.
- 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:
- Adicione seus IDs de Locais Iniciais copiados como chaves.
- Adicione IDs de Universos como o primeiro elemento do valor da tupla.
- 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.
Execute o seguinte comando para rodar o bot:
Executar Bot do Discordpython3 discord_bot.pyO 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:
Envie uma solicitação HTTP POST para o seu servidor de webhook do Discord com o seguinte corpo de solicitação:
Exemplo de Solicitaçãocurl -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}"}}]}'Se você tiver um segredo de webhook:
- Gere uma Roblox-Signature aplicando a codificação HMAC-SHA256 à sua chave secreta do webhook.
- Defina a hora atual usando o timestamp UTC em segundos como Timestamp.
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ção1683927229. 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.
{
"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"
}
}
]
}