Automatiser le droit à l'effacement à l'aide de webhooks

*Ce contenu est traduit en utilisant l'IA (Beta) et peut contenir des erreurs. Pour consulter cette page en anglais, clique ici.

Les réglementations mondiales sur la protection des données et la vie privée accordent aux individus le droit de contrôler leurs données, y compris le droit de demander leur suppression (souvent appelé "droit à l'effacement" ou "droit à supprimer"). Si vous stockez des données personnelles ou des Informations Personnellement Identifiables (PII), telles que des identifiants d'utilisateur, vous êtes responsable de la conformité avec le(s) cadre(s) de confidentialité applicable(s) en supprimant définitivement ces informations dès réception d'une demande d'utilisateur. Plus d'informations peuvent être trouvées dans RTBF et créateurs.

Envisagez des alternatives

Pour les données personnelles dans les magasins de données, l'option d'automatisation la plus simple consiste à configurer des modèles de suppression RTBF. Lorsque Roblox traite une demande RTBF, il supprime automatiquement les entrées de magasin de données standard ou ordonnées correspondantes sans nécessiter que vous hébergiez du code personnalisé. Pour configurer des modèles et examiner les modèles pris en charge, consultez Droit à l'oubli (RTBF) des magasins de données.

Le flux de travail de webhook sur cette page est utile lorsque :

  • Vous stockez des données personnelles en dehors des magasins de données.
  • Votre schéma de magasin de données ou votre cas d'utilisation n'est pas pris en charge par les modèles de suppression RTBF.
  • Vous avez besoin d'une logique de traitement ou de suppression personnalisée.

Flux de travail du webhook créateur

Ce tutoriel démontre comment configurer un webhook et créer un bot Discord fonctionnant localement qui utilise l'API Open Cloud pour les magasins de données pour automatiser le traitement des demandes de droit à l'effacement. Le flux de travail pour ce processus est le suivant :

  1. Le support Roblox reçoit une demande de droit à l'effacement d'un utilisateur.
  2. Le webhook Roblox est déclenché, contenant l'identifiant de l'utilisateur et une liste d'identifiants de lieu de départ pour les jeux auxquels ils ont participé dans la charge utile.
  3. Votre bot écoute ces notifications de webhook, vérifie leur authenticité et utilise l'API Open Cloud pour les magasins de données pour supprimer les données PII stockées dans les magasins de données.
  4. Le bot répond au message du webhook dans Discord avec le statut de suppression.

Configurer un webhook avec une intégration tierce

Avant de créer un bot, configurez un serveur avec une intégration de webhook sur l'application de messagerie tierce. Ensuite, utilisez le serveur pour configurer un webhook sur le tableau de bord des créateurs.

Configurer un serveur

Les étapes suivantes montrent comment configurer le serveur en utilisant Discord.

  1. Créez un nouveau serveur Discord. Si vous n'êtes pas familier avec le processus, consultez Support Discord.

  2. Le serveur crée automatiquement un canal #général comme votre canal par défaut. Cliquez sur l'icône Modifier le canal du canal #général.

  3. Sous Permissions, définissez le canal comme privé.

  4. Créez une intégration de webhook avec le nouveau serveur, nommez-la RTBF Hook. Si vous n'êtes pas familier avec le processus, consultez Support Discord.

  5. Copiez l'URL du webhook et conservez-la dans un endroit sûr. N'autorisez que les membres de l'équipe de confiance à y accéder, car la fuite de l'URL peut permettre à des acteurs malveillants d'envoyer de faux messages et potentiellement de supprimer vos données utilisateur.

Configurer un webhook sur Roblox

Après avoir obtenu l'URL du serveur tiers, utilisez-la pour configurer un webhook sur le tableau de bord des créateurs. Assurez-vous d'effectuer les paramètres suivants :

  • Ajoutez l'URL du serveur Discord comme URL du webhook.
  • Incluez un Secret personnalisé. Bien qu'un secret soit facultatif pour compléter la configuration, vous devriez en inclure un pour empêcher les acteurs malveillants d'usurper l'identité de Roblox et de supprimer vos données. Pour plus d'informations sur l'utilisation d'un secret, consultez Vérifier la sécurité du webhook.
  • Sélectionnez Demande de droit à l'effacement sous Déclencheurs.

Vous pouvez tester le webhook en utilisant le bouton Tester la réponse pour voir si vous recevez une notification dans le canal #général de votre serveur de la part de Roblox. Si vous ne recevez pas la notification, réessayez ou vérifiez les paramètres de votre serveur pour résoudre l'erreur.

Configurer un bot

Après avoir ajouté le webhook, utilisez-le pour configurer le bot avec les étapes suivantes. Pour plus d'informations, consultez la documentation Discord.

  1. Accédez à la page des applications.
  2. Créez une nouvelle application et nommez-la RTBF Bot.
  3. Le système vous redirige vers les paramètres Informations générales du bot. Copiez et enregistrez son ID d'application dans un endroit sûr.
  4. Sous le menu des paramètres, sélectionnez OAuth2.
  5. Accédez aux paramètres OAuth2
    1. Activez le scope bot, une liste d'autorisations supplémentaires pour le bot s'affiche.
    2. Ajoutez l'autorisation Administrateur. Enregistrez vos modifications.
    3. Enregistrez l'URL générée.
  6. Copiez l'URL générée. Laissez la page des paramètres de l'application ouverte.
  7. Accédez à l'URL générée. Sélectionnez le serveur cible. Cliquez sur le bouton Continuer puis sur le bouton Autoriser.
  8. Revenez à la page des paramètres de l'application et accédez aux paramètres du bot.
  9. Sous la section Intentions de passerelle privilégiées, activez Intent de contenu de message.
  10. Dans les paramètres du bot > section Construire un bot, enregistrez le jeton du bot dans un endroit sûr pour les étapes ultérieures. Si vous ne voyez pas le jeton, cliquez sur le bouton Réinitialiser le jeton pour en générer un nouveau.

Créer une clé API Open Cloud

Pour permettre à votre bot tiers d'accéder à vos magasins de données pour stocker les données PII des utilisateurs, créez une clé API Open Cloud qui peut accéder à vos jeux et ajoutez la permission Supprimer l'entrée des magasins de données pour la suppression des données. Si vous utilisez des magasins de données ordonnés pour stocker des PII, vous devez également ajouter la permission Écrire des magasins de données ordonnés. Une fois terminé, copiez et enregistrez la clé API dans un endroit sûr pour l'utiliser dans les étapes ultérieures.

Obtenir les identifiants des jeux et des lieux

Pour que le bot puisse localiser les données PII demandées par les utilisateurs pour suppression, obtenez les identifiants suivants de tous les jeux pour lesquels vous avez l'intention d'utiliser le bot :

  • L'ID de l'univers, l'identifiant unique de votre jeu.
  • L'ID de lieu de départ, l'identifiant unique du lieu de départ d'un jeu.

Pour obtenir ces identifiants :

  1. Survolez la miniature d'un jeu, cliquez sur le bouton et sélectionnez Copier l'ID de l'univers et Copier l'ID de lieu de départ, respectivement.

Ajouter des scripts

Après avoir terminé la configuration du webhook, du bot et de la clé API pour les magasins de données, ajoutez-les aux scripts qui mettent en œuvre la logique d'automatisation du bot. L'exemple suivant utilise Python 3.

  1. Installez les bibliothèques Python en utilisant les commandes suivantes :

    Installer les bibliothèques
    pip3 install discord
    pip3 install requests
    pip3 install urllib3==1.26.6
  2. Copiez et enregistrez les scripts suivants correspondant à différentes parties de la logique du bot dans le même répertoire :

    bot_config.py
    BOT_TOKEN = ""
    OPEN_CLOUD_API_KEY = ""
    ROBLOX_WEBHOOK_SECRET = ""
    # Dictionnaire de l'ID de lieu de départ à
    # (ID de l'univers, liste de (nom des magasins de données, portée, et clé d'entrée)) pour
    # Magasins de données standard
    # Les données utilisateur stockées sous ces entrées seront supprimées
    STANDARD_DATA_STORE_ENTRIES = {
    # ID de lieu de départ
    111111111: (
    # ID de l'univers
    222222222,
    [
    ("StandardDataStore1", "Scope1", "Key1_{user_id}"),
    ("StandardDataStore1", "Scope1", "Key2_{user_id}"),
    ("StandardDataStore2", "Scope1", "Key3_{user_id}")
    ]
    ),
    33333333: (
    444444444,
    [
    ("StandardDataStore3", "Scope1", "Key1_{user_id}")
    ]
    )
    }
    # Dictionnaire de l'ID de lieu de départ à
    # (ID de l'univers, liste de (nom des magasins de données, portée, et clé d'entrée)) pour
    # Magasins de données ordonnés
    # Les données utilisateur stockées sous ces entrées seront supprimées
    ORDERED_DATA_STORE_ENTRIES = {
    111111111: (
    222222222,
    [
    ("OrderedDataStore1", "Scope2", "Key4_{user_id}")
    ]
    )
    }
    data_stores_api.py
    import requests
    import bot_config
    from collections import defaultdict
    """
    Appelle l'API Open Cloud des magasins de données pour supprimer toutes les entrées pour un user_id configuré dans
    STANDARD_DATA_STORE_ENTRIES. Renvoie une liste de suppressions réussies et d'échecs de suppression.
    """
    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
    """
    Appelle l'API Open Cloud des magasins de données ordonnés pour supprimer toutes les entrées pour un user_id configuré dans
    ORDERED_DATA_STORE_ENTRIES. Renvoie une liste de suppressions réussies et d'échecs de suppression.
    """
    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
    """
    Analyse le message reçu pour la signature et l'horodatage de Roblox, le pied de page n'est défini que si vous
    avez configuré le secret du 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
    """
    Vérifie la signature de Roblox avec le secret configuré pour vérifier sa validité
    """
    def validate_signature(message, signature, timestamp):
    if not message or not signature or not timestamp:
    return False
    # Empêche les attaques par rejeu dans une fenêtre de 300 secondes
    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
    # Valide la signature
    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
    # Signature valide
    return True
    """
    Analyse un message de webhook reçu sur Discord. Extrait l'ID utilisateur, empêche les attaques par rejeu
    basées sur l'horodatage reçu, et vérifie la signature de Roblox avec le secret configuré pour vérifier sa validité.
    """
    def parse_message(message):
    # Analyse le message reçu pour l'ID utilisateur et l'ID de jeu
    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} écoute les messages de droit à l'effacement")
    """
    Gestionnaire pour les messages de webhook de Roblox
    """
    @client.event
    async def on_message(message):
    # Analyse et valide le message
    user_id, start_place_ids = message_parser.parse_message(message)
    if not user_id or not start_place_ids:
    return
    # Supprime les données utilisateur des magasins de données standard
    [successes, failures] = data_stores_api.delete_standard_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Données des magasins de données standard supprimées pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(successes)}")
    if failures:
    await message.reply(f"Échec de la suppression des données des magasins de données standard pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(failures)}")
    # Supprime les données utilisateur des magasins de données ordonnés
    [successes, failures] = data_stores_api.delete_ordered_data_stores(user_id, start_place_ids)
    if successes:
    await message.reply(f"Données des magasins de données ordonnés supprimées pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(successes)}")
    if failures:
    await message.reply(f"Échec de la suppression des données des magasins de données ordonnés pour " +
    f"l'ID utilisateur : {user_id}, données : {dict(failures)}")
    client.run(bot_config.BOT_TOKEN)
    if __name__ == "__main__":
    run()
  3. Dans le fichier bot_config.py pour la configuration principale du bot :

    1. Définissez BOT_TOKEN sur le jeton généré par votre bot.
    2. Définissez OPEN_CLOUD_API_KEY comme la clé API que vous avez créée.
    3. Définissez ROBLOX_WEBHOOK_SECRET comme le secret que vous avez défini lors de la configuration du webhook sur le tableau de bord des créateurs.
    4. Dans les dictionnaires STANDARD_DATA_STORE_ENTRIES et ORDERED_DATA_STORE_ENTRIES pour localiser le magasin de données de chaque enregistrement à supprimer :
      1. Ajoutez vos ID de lieu de départ copiés comme clés.
      2. Ajoutez les ID d'univers comme premier élément de la valeur du tuple.
      3. Remplacez le deuxième élément du tuple par le nom, la portée, le nom de la clé d'entrée et l'ID utilisateur associé de vos magasins de données. Si vous utilisez un schéma de données différent, modifiez-le pour correspondre à votre propre schéma de données en conséquence.
  4. Exécutez la commande suivante pour exécuter le bot :

    Exécuter le bot Discord
    python3 discord_bot.py
  5. Le bot commence alors à écouter et à vérifier les webhooks Roblox pour les demandes de droit à l'effacement et appelle le point de terminaison Open Cloud pour supprimer le magasin de données correspondant.

Tester

Vous pouvez créer et exécuter un message de test pour vérifier que votre programme personnalisé peut gérer correctement les demandes de droit à l'effacement et supprimer les données PII :

  1. Envoyez une requête HTTP POST à votre serveur webhook Discord avec le corps de requête suivant :

    Exemple de requête
    curl -X POST {serverUrl}
    -H 'Content-Type: application/json'
    -d '{
    "embeds":[{
    "title":"RightToErasureRequest",
    "description":"Vous avez reçu une nouvelle notification pour le droit à l'effacement pour l'ID utilisateur : {userIds} dans le(s) jeu(x) avec les ID : {gameIds}",
    "footer":{
    "icon_url":"https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
    "text":"Roblox-Signature: {robloxSignature}, Timestamp: {timestamp}"
    }
    }]
    }'
  2. Si vous avez un secret de webhook :

    1. Générez une Roblox-Signature en appliquant un encodage HMAC-SHA256 à votre clé secrète de webhook.
    2. Définissez l'heure actuelle en utilisant l'horodatage UTC en secondes comme Timestamp.
  3. Assemblez la description dans le format suivant :

    Format du champ Description
    {Timestamp}. Vous avez reçu une nouvelle notification pour le droit à l'effacement pour l'ID utilisateur : {userId} dans le(s) jeu(x) avec les ID : {gameIds}`.

    Par exemple :

    Exemple de champ Description
    1683927229. Vous avez reçu une nouvelle notification pour le droit à l'effacement pour l'ID utilisateur : 2425654247 dans le(s) jeu(x) avec les ID : 10539205763, 13260950955

Votre programme devrait être capable d'identifier que votre message provient de la source officielle de Roblox puisque vous avez encodé le message avec votre secret. Il devrait ensuite supprimer les données PII associées à votre demande.

Exemple de corps
{
"embeds": [
{
"title": "RightToErasureRequest",
"description": "Vous avez reçu une nouvelle notification pour le droit à l'effacement pour l'ID utilisateur : 2425654247 dans le(s) jeu(x) avec les ID : 10539205763, 13260950955",
"footer": {
"icon_url": "https://create.roblox.com/dashboard/assets/webhooks/roblox_logo_metal.png",
"text": "Roblox-Signature: UIe6GJ78MHCmU/zUKBYP3LV0lAqwWRFR6UEfPt1xBFw=, Timestamp: 1683927229"
}
}
]
}
©2026 Société Roblox. Roblox, le logo Roblox et Powering Imagination font partie de nos marques déposées aux États-Unis et dans d'autres pays.