{"meta":{"title":"Automatische erneute Lieferung fehlgeschlagener Zustellungen für einen Repository-Webhook","intro":"Du kannst ein Skript schreiben, um fehlgeschlagene Zustellungen eines Repositorywebhooks zu verarbeiten.","product":"webhooks","breadcrumbs":[{"href":"/de/webhooks","title":"webhooks"},{"href":"/de/webhooks/using-webhooks","title":"Verwenden von Webhooks"},{"href":"/de/webhooks/using-webhooks/automatically-redelivering-failed-deliveries-for-a-repository-webhook","title":"Automatisches erneutes Zustellen für Repository"}],"documentType":"article"},"body":"# Automatische erneute Lieferung fehlgeschlagener Zustellungen für einen Repository-Webhook\n\nDu kannst ein Skript schreiben, um fehlgeschlagene Zustellungen eines Repositorywebhooks zu verarbeiten.\n\n## Informationen zur automatischen erneuten Zustellung fehlgeschlagener Zustellungen\n\nIn diesem Artikel wird beschrieben, wie du ein Skript schreibst, um fehlgeschlagene Zustellungen für einen Repositorywebhook zu finden und erneut zu senden. Weitere Informationen zu fehlgeschlagenen Zustellungen findest du unter [Bearbeitung fehlgeschlagener Webhook-Zustellungen](/de/webhooks/using-webhooks/handling-failed-webhook-deliveries).\n\nIn diesem Beispiel wird Folgendes angezeigt:\n\n* Ein Skript, das fehlgeschlagene Zustellungen für einen Repositorywebhook finden und erneut senden kann\n* Welche Anmeldeinformationen Ihr Skript benötigt, und wie Sie die Anmeldeinformationen sicher als GitHub Actions geheime Schlüssel speichern\n* Ein GitHub Actions Workflow, der sicher auf Ihre Anmeldeinformationen zugreifen und das Skript regelmäßig ausführen kann\n\nIn diesem Beispiel wird GitHub Actions verwendet, aber Sie können dieses Skript auch auf Ihrem Server ausführen, der Webhook-Lieferungen verarbeitet. Weitere Informationen findest du unter [Alternativen Methoden](#alternative-methods).\n\n## Speichern von Anmeldeinformationen für das Skript\n\nDas integrierte `GITHUB_TOKEN` verfügt nicht über ausreichende Berechtigungen, um Webhooks erneut zuzustellen. Anstelle der Verwendung von `GITHUB_TOKEN` wird in diesem Beispiel ein personal access token verwendet. Alternativ können Sie, anstelle eine personal access token zu erstellen, eine GitHub App-App erstellen und die Anmeldeinformationen der App verwenden, um während des GitHub Actions-Workflows ein Installationszugriffstoken zu erstellen. Weitere Informationen finden Sie unter [Erstellen authentifizierter API-Anforderungen mit einer GitHub App in einem GitHub Actions-Workflow](/de/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow).\n\n1. Erstellen Sie einen personal access token mit folgender Zugriffsmöglichkeit. Weitere Informationen finden Sie unter [Verwalten deiner persönlichen Zugriffstoken](/de/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).\n   * Erteilen Sie ein fine-grained personal access token-Token:\n     * Zugriff auf das Repository, in dem dein Webhook erstellt wurde\n     * Zugriff auf das Repository, in dem dieser Workflow ausgeführt wird\n     * Schreibzugriff auf die Repositorywebhooks-Berechtigung\n     * Schreibzugriff auf die Repositoryvariablen-Berechtigung\n   * Gewähren Sie dem Token personal access token (classic) den `repo`-Bereich.\n2. Speichern Sie Ihr personal access token als ein GitHub Actions Geheimnis im Repository, in dem der Workflow ausgeführt werden soll. Weitere Informationen finden Sie unter [Verwenden von Geheimnissen in GitHub-Aktionen](/de/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets).\n\n## Hinzufügen eines Workflows, der das Skript ausführt\n\nIn diesem Abschnitt wird veranschaulicht, wie Sie einen GitHub Actions Workflow verwenden können, um sicher auf die Anmeldeinformationen zuzugreifen, die Sie im vorherigen Abschnitt gespeichert haben, Umgebungsvariablen festzulegen und regelmäßig ein Skript auszuführen, um fehlerhafte Lieferungen zu finden und zu redeliver führen.\n\nKopieren Sie diesen GitHub Actions Workflow in eine YAML-Datei im `.github/workflows` Verzeichnis im Repository, in dem der Workflow ausgeführt werden soll. Ersetzen Sie die Platzhalter im `Run script`-Schritt wie unten beschrieben.\n\n```yaml copy annotate\n#\nname: Redeliver failed webhook deliveries\n\n# This workflow runs every 6 hours or when manually triggered.\non:\n  schedule:\n    - cron: '20 */6 * * *'\n  workflow_dispatch:\n\n# This workflow will use the built in `GITHUB_TOKEN` to check out the repository contents. This grants `GITHUB_TOKEN` permission to do that.\npermissions:\n  contents: read\n\n#\njobs:\n  redeliver-failed-deliveries:\n    name: Redeliver failed deliveries\n    runs-on: ubuntu-latest\n    steps:\n      # This workflow will run a script that is stored in the repository. This step checks out the repository contents so that the workflow can access the script.\n      - name: Check out repo content\n        uses: actions/checkout@v6\n\n      # This step sets up Node.js. The script that this workflow will run uses Node.js.\n      - name: Setup Node.js\n        uses: actions/setup-node@v7\n        with:\n          node-version: '18.x'\n\n      # This step installs the octokit library. The script that this workflow will run uses the octokit library.\n      - name: Install dependencies\n        run: npm install octokit\n\n      # This step sets some environment variables, then runs a script to find and redeliver failed webhook deliveries.\n      # - Replace `YOUR_SECRET_NAME` with the name of the secret where you stored your personal access token.\n      # - Replace `YOUR_REPO_OWNER` with the owner of the repository where the webhook was created.\n      # - Replace `YOUR_REPO_NAME` with the name of the repository where the webhook was created.\n      # - Replace `YOUR_HOOK_ID` with the ID of the webhook.\n      # - Replace `YOUR_LAST_REDELIVERY_VARIABLE_NAME` with the name that you want to use for a configuration variable that will be stored in the repository where this workflow is stored. The name can be any string that contains only alphanumeric characters and `_`, and does not start with `GITHUB_` or a number. For more information, see [AUTOTITLE](/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows).\n      \n      - name: Run script\n        env:\n          TOKEN: ${{ secrets.YOUR_SECRET_NAME }}\n          REPO_OWNER: 'YOUR_REPO_OWNER'\n          REPO_NAME: 'YOUR_REPO_NAME'\n          HOOK_ID: 'YOUR_HOOK_ID'\n          LAST_REDELIVERY_VARIABLE_NAME: 'YOUR_LAST_REDELIVERY_VARIABLE_NAME'\n          \n          WORKFLOW_REPO_NAME: ${{ github.event.repository.name }}\n          WORKFLOW_REPO_OWNER: ${{ github.repository_owner }}\n        run: |\n          node .github/workflows/scripts/redeliver-failed-deliveries.js\n```\n\n## Hinzufügen des Skripts\n\nIn diesem Abschnitt wird veranschaulicht, wie du ein Skript schreiben kannst, um fehlgeschlagene Zustellungen zu finden und erneut zu senden.\n\nKopieren Sie dieses Skript in eine Datei mit dem Namen `.github/workflows/scripts/redeliver-failed-deliveries.js` im selben Repository, in dem Sie die GitHub Actions-Workflowdatei gespeichert haben.\n\n```javascript copy annotate\n// This script uses GitHub's Octokit SDK to make API requests. For more information, see [AUTOTITLE](/rest/guides/scripting-with-the-rest-api-and-javascript).\nconst { Octokit } = require(\"octokit\");\n\n//\nasync function checkAndRedeliverWebhooks() {\n  // Get the values of environment variables that were set by the GitHub Actions workflow.\n  const TOKEN = process.env.TOKEN;\n  const REPO_OWNER = process.env.REPO_OWNER;\n  const REPO_NAME = process.env.REPO_NAME;\n  const HOOK_ID = process.env.HOOK_ID;\n  const LAST_REDELIVERY_VARIABLE_NAME = process.env.LAST_REDELIVERY_VARIABLE_NAME;\n  \n  const WORKFLOW_REPO_NAME = process.env.WORKFLOW_REPO_NAME;\n  const WORKFLOW_REPO_OWNER = process.env.WORKFLOW_REPO_OWNER;\n\n  // Create an instance of `Octokit` using the token values that were set in the GitHub Actions workflow.\n  const octokit = new Octokit({ \n    auth: TOKEN,\n  });\n\n  try {\n    // Get the last time that this script ran from the configuration variable. If the variable is not defined, use the current time minus 24 hours.\n    const lastStoredRedeliveryTime = await getVariable({\n      variableName: LAST_REDELIVERY_VARIABLE_NAME,\n      repoOwner: WORKFLOW_REPO_OWNER,\n      repoName: WORKFLOW_REPO_NAME,\n      octokit,\n    });\n    const lastWebhookRedeliveryTime = lastStoredRedeliveryTime || (Date.now() - (24 * 60 * 60 * 1000)).toString();\n\n    // Record the time that this script started redelivering webhooks.\n    const newWebhookRedeliveryTime = Date.now().toString();\n\n    // Get the webhook deliveries that were delivered after `lastWebhookRedeliveryTime`.\n    const deliveries = await fetchWebhookDeliveriesSince({\n      lastWebhookRedeliveryTime,\n      repoOwner: REPO_OWNER,\n      repoName: REPO_NAME,\n      hookId: HOOK_ID,\n      octokit,\n    });\n\n    // Consolidate deliveries that have the same globally unique identifier (GUID). The GUID is constant across redeliveries of the same delivery.\n    let deliveriesByGuid = {};\n    for (const delivery of deliveries) {\n      deliveriesByGuid[delivery.guid]\n        ? deliveriesByGuid[delivery.guid].push(delivery)\n        : (deliveriesByGuid[delivery.guid] = [delivery]);\n    }\n\n    // For each GUID value, if no deliveries for that GUID have been successfully delivered within the time frame, get the delivery ID of one of the deliveries with that GUID.\n    //\n    // This will prevent duplicate redeliveries if a delivery has failed multiple times.\n    // This will also prevent redelivery of failed deliveries that have already been successfully redelivered.\n    let failedDeliveryIDs = [];\n    for (const guid in deliveriesByGuid) {\n      const deliveries = deliveriesByGuid[guid];\n      const anySucceeded = deliveries.some(\n        (delivery) => delivery.status === \"OK\"\n      );\n      if (!anySucceeded) {\n        failedDeliveryIDs.push(deliveries[0].id);\n      }\n    }\n\n    // Redeliver any failed deliveries.\n    for (const deliveryId of failedDeliveryIDs) {\n      await redeliverWebhook({\n        deliveryId,\n        repoOwner: REPO_OWNER,\n        repoName: REPO_NAME,\n        hookId: HOOK_ID,\n        octokit,\n      });\n    }\n\n    // Update the configuration variable (or create the variable if it doesn't already exist) to store the time that this script started.\n    // This value will be used next time this script runs.\n    await updateVariable({\n      variableName: LAST_REDELIVERY_VARIABLE_NAME,\n      value: newWebhookRedeliveryTime,\n      variableExists: Boolean(lastStoredRedeliveryTime),\n      repoOwner: WORKFLOW_REPO_OWNER,\n      repoName: WORKFLOW_REPO_NAME,\n      octokit,\n    });\n\n    // Log the number of redeliveries.\n    console.log(\n      `Redelivered ${\n        failedDeliveryIDs.length\n      } failed webhook deliveries out of ${\n        deliveries.length\n      } total deliveries since ${new Date(Number(lastWebhookRedeliveryTime))}.`\n    );\n  } catch (error) {\n    // If there was an error, log the error so that it appears in the workflow run log, then throw the error so that the workflow run registers as a failure.\n    if (error.response) {\n      console.error(\n        `Failed to check and redeliver webhooks: ${error.response.data.message}`\n      );\n    }\n    console.error(error);\n    throw(error);\n  }\n}\n\n// This function will fetch all of the webhook deliveries that were delivered since `lastWebhookRedeliveryTime`.\n// It uses the `octokit.paginate.iterator()` method to iterate through paginated results. For more information, see [AUTOTITLE](/rest/guides/scripting-with-the-rest-api-and-javascript#making-paginated-requests).\n//\n// If a page of results includes deliveries that occurred before `lastWebhookRedeliveryTime`,\n// it will store only the deliveries that occurred after `lastWebhookRedeliveryTime` and then stop.\n// Otherwise, it will store all of the deliveries from the page and request the next page.\nasync function fetchWebhookDeliveriesSince({\n  lastWebhookRedeliveryTime,\n  repoOwner,\n  repoName,\n  hookId,\n  octokit,\n}) {\n  const iterator = octokit.paginate.iterator(\n    \"GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries\",\n    {\n      owner: repoOwner,\n      repo: repoName,\n      hook_id: hookId,\n      per_page: 100,\n      headers: {\n        \"x-github-api-version\": \"2026-03-10\",\n      },\n    }\n  );\n\n  const deliveries = [];\n\n  for await (const { data } of iterator) {\n    const oldestDeliveryTimestamp = new Date(\n      data[data.length - 1].delivered_at\n    ).getTime();\n\n    if (oldestDeliveryTimestamp < lastWebhookRedeliveryTime) {\n      for (const delivery of data) {\n        if (\n          new Date(delivery.delivered_at).getTime() > lastWebhookRedeliveryTime\n        ) {\n          deliveries.push(delivery);\n        } else {\n          break;\n        }\n      }\n      break;\n    } else {\n      deliveries.push(...data);\n    }\n  }\n\n  return deliveries;\n}\n\n// This function will redeliver a failed webhook delivery.\nasync function redeliverWebhook({\n  deliveryId,\n  repoOwner,\n  repoName,\n  hookId,\n  octokit,\n}) {\n  await octokit.request(\n    \"POST /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts\",\n    {\n      owner: repoOwner,\n      repo: repoName,\n      hook_id: hookId,\n      delivery_id: deliveryId,\n    }\n  );\n}\n\n// This function gets the value of a configuration variable.\n// If the variable does not exist, the endpoint returns a 404 response and this function returns `undefined`.\nasync function getVariable({ variableName, repoOwner, repoName, octokit }) {\n  try {\n    const {\n      data: { value },\n    } = await octokit.request(\n      \"GET /repos/{owner}/{repo}/actions/variables/{name}\",\n      {\n        owner: repoOwner,\n        repo: repoName,\n        name: variableName,\n      }\n    );\n    return value;\n  } catch (error) {\n    if (error.status === 404) {\n      return undefined;\n    } else {\n      throw error;\n    }\n  }\n}\n\n// This function will update a configuration variable (or create the variable if it doesn't already exist). For more information, see [AUTOTITLE](/actions/learn-github-actions/variables#defining-configuration-variables-for-multiple-workflows).\nasync function updateVariable({\n  variableName,\n  value,\n  variableExists,\n  repoOwner,\n  repoName,\n  octokit,\n}) {\n  if (variableExists) {\n    await octokit.request(\n      \"PATCH /repos/{owner}/{repo}/actions/variables/{name}\",\n      {\n        owner: repoOwner,\n        repo: repoName,\n        name: variableName,\n        value: value,\n      }\n    );\n  } else {\n    await octokit.request(\"POST /repos/{owner}/{repo}/actions/variables\", {\n      owner: repoOwner,\n      repo: repoName,\n      name: variableName,\n      value: value,\n    });\n  }\n}\n\n// This will execute the `checkAndRedeliverWebhooks` function.\n(async () => {\n  await checkAndRedeliverWebhooks();\n})();\n\n```\n\n## Testen des Skripts\n\nDu kannst deinen Workflow manuell auslösen, um das Skript zu testen. Weitere Informationen findest du unter [Manuelles Ausführen eines Workflows](/de/actions/how-tos/manage-workflow-runs/manually-run-a-workflow) und [Verwenden von Workflowausführungsprotokollen](/de/actions/how-tos/monitor-workflows/use-workflow-run-logs).\n\n## Alternative Methoden\n\nIn diesem Beispiel wird GitHub Actions verwendet, um Anmeldeinformationen sicher zu speichern und das Skript nach einem Zeitplan auszuführen. Wenn du dieses Skript jedoch lieber auf deinem Server ausführen möchtest, das Webhookübermittlungen verarbeitet, kannst du Folgendes tun:\n\n* Speichern Sie die Anmeldeinformationen auf eine andere sichere Weise, z. B. einen geheimen Manager wie [Azure Key Vault](https://azure.microsoft.com/products/key-vault). Du musst auch das Skript aktualisieren, um von ihrem neuen Speicherort aus auf die Anmeldeinformationen zuzugreifen.\n* Führe das Skript nach einem Zeitplan auf deinem Server aus, z. B. mithilfe eines Cron-Auftrags oder eines Aufgabenplaners.\n* Aktualisiere das Skript so, dass die letzte Laufzeit an einem Ort gespeichert wird, auf den dein Server zugreifen und den er aktualisieren kann. Wenn Sie sich entscheiden, die letzte Laufzeit nicht als GitHub Actions geheimer Schlüssel zu speichern, können Sie die API-Aufrufe entfernen, um auf die Konfigurationsvariable zuzugreifen und sie zu aktualisieren."}