{"meta":{"title":"Reentrega automática de entregas fallidas para un webhook de una aplicación de GitHub","intro":"Puede escribir un script para gestionar las entregas fallidas de un webhook GitHub App.","product":"Webhooks","breadcrumbs":[{"href":"/es/webhooks","title":"Webhooks"},{"href":"/es/webhooks/using-webhooks","title":"Uso de webhooks"},{"href":"/es/webhooks/using-webhooks/automatically-redelivering-failed-deliveries-for-a-github-app-webhook","title":"Volver a entregar automáticamente para GitHub App"}],"documentType":"article"},"body":"# Reentrega automática de entregas fallidas para un webhook de una aplicación de GitHub\n\nPuede escribir un script para gestionar las entregas fallidas de un webhook GitHub App.\n\n## Acerca de la reentrega automática de entregas erróneas\n\nEn este artículo se describe cómo escribir un script para buscar y volver a entregar entregas erróneas para un GitHub App webhook. Para más información sobre las entregas erróneas, consulta [Gestión de entregas fallidas de webhooks](/es/webhooks/using-webhooks/handling-failed-webhook-deliveries).\n\nEste ejemplo te muestra:\n\n* Un script que buscará y reenviará las entregas fallidas de un GitHub App webhook\n* Qué credenciales necesitará el script y cómo almacenar las credenciales de forma segura como GitHub Actions secretos\n* Flujo GitHub Actions de trabajo que puede acceder de forma segura a sus credenciales y ejecutar el script periódicamente\n\nEn este ejemplo se usa GitHub Actions, pero también puede ejecutar este script en el servidor que controla las entregas de webhook. Para obtener más información, consulta [Métodos alternativos](#alternative-methods).\n\n## Almacenamiento de credenciales para el script\n\nLos puntos de conexión para buscar y volver a entregar webhooks requieren un token web JSON, que se genera a partir del identificador de aplicación y la clave privada de la aplicación.\n\nLos puntos de conexión para capturar y actualizar el valor de las variables de entorno requieren un personal access tokenGitHub App token de acceso de instalación o GitHub App un token de acceso de usuario. Este ejemplo usa un personal access token. Si su GitHub App está instalado en el repositorio donde se ejecutará este flujo de trabajo y tiene permiso para escribir variables del repositorio, puede modificar este ejemplo para crear un token de acceso de instalación durante el flujo de trabajo GitHub Actions en lugar de usar un personal access token. Para más información, consulta [Realización de solicitudes de API autenticadas con una aplicación de GitHub en un flujo de trabajo de Acciones de GitHub](/es/apps/creating-github-apps/authenticating-with-a-github-app/making-authenticated-api-requests-with-a-github-app-in-a-github-actions-workflow).\n\n1. Busca el ID de la aplicación de tu GitHub App. Puedes encontrar el id. de la aplicación en la página de configuración de la aplicación. El id. de la aplicación es diferente del id. de cliente. Para obtener más información sobre cómo navegar a la página de configuración de GitHub App, vea [Modificación de un registro de aplicación de GitHub](/es/apps/maintaining-github-apps/modifying-a-github-app-registration#navigating-to-your-github-app-settings).\n2. Almacene el identificador de la aplicación del paso anterior como un GitHub Actions secreto en el repositorio donde desea que se ejecute el flujo de trabajo. Para obtener más información sobre cómo almacenar secretos, consulta [Uso de secretos en Acciones de GitHub](/es/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets).\n3. Generar una llave privada para tu app. Para obtener más información sobre cómo generar una clave privada, consulte [Administración de claves privadas para aplicaciones de GitHub](/es/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps).\n4. Almacene la clave privada, incluida `-----BEGIN RSA PRIVATE KEY-----` y `-----END RSA PRIVATE KEY-----`, del paso anterior como un GitHub Actions secreto en el repositorio donde desea que se ejecute el flujo de trabajo.\n5. Cree un personal access token con el siguiente acceso. Para más información, consulta [Administración de tokens de acceso personal](/es/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).\n   * Para un fine-grained personal access token, concede el token:\n     * Acceso de escritura al permiso de variables del repositorio\n     * Acceder al repositorio donde se ejecutará este flujo de trabajo\n   * Para un personal access token (classic), conceda al token el alcance `repo`.\n6. Guarda tu personal access token del paso anterior como un secreto GitHub Actions en el repositorio donde quieras que se ejecute el flujo de trabajo.\n\n## Agregar un flujo de trabajo que ejecutará el script\n\nEn esta sección se muestra cómo puede usar un GitHub Actions flujo de trabajo para acceder de forma segura a las credenciales almacenadas en la sección anterior, establecer variables de entorno y ejecutar periódicamente un script para buscar y volver a entregar entregas con errores.\n\nCopie este GitHub Actions flujo de trabajo en un archivo YAML en el `.github/workflows` directorio del repositorio donde desea que se ejecute el flujo de trabajo. Reemplace los marcadores de posición en el paso `Run script` como se describe a continuación.\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: '40 */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: '20.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_APP_ID_SECRET_NAME` with the name of the secret where you stored your app ID.\n      # - Replace `YOUR_PRIVATE_KEY_SECRET_NAME` with the name of the secret where you stored your private key.\n      # - Replace `YOUR_TOKEN_SECRET_NAME` with the name of the secret where you stored your personal access token.\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          APP_ID: ${{ secrets.YOUR_APP_ID_SECRET_NAME }}\n          PRIVATE_KEY: ${{ secrets.YOUR_PRIVATE_KEY_SECRET_NAME }}\n          TOKEN: ${{ secrets.YOUR_TOKEN_SECRET_NAME }}\n          LAST_REDELIVERY_VARIABLE_NAME: 'YOUR_LAST_REDELIVERY_VARIABLE_NAME'\n          \n          WORKFLOW_REPO: ${{ github.event.repository.name }}\n          WORKFLOW_REPO_OWNER: ${{ github.repository_owner }}\n        run: |\n          node .github/workflows/scripts/redeliver-failed-deliveries.mjs\n```\n\n## Añadir el script\n\nEn esta sección se muestra cómo puedes escribir un script para buscar y volver a entregar entregas fallidas.\n\nCopie este script en un archivo llamado `.github/workflows/scripts/redeliver-failed-deliveries.mjs` en el mismo repositorio donde guardó el archivo GitHub Actions de flujo de trabajo anterior.\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).\nimport { App, Octokit } from \"octokit\";\n\n//\nasync function checkAndRedeliverWebhooks() {\n  // Get the values of environment variables that were set by the GitHub Actions workflow.\n  const APP_ID = process.env.APP_ID;\n  const PRIVATE_KEY = process.env.PRIVATE_KEY;\n  const TOKEN = process.env.TOKEN;\n  const LAST_REDELIVERY_VARIABLE_NAME = process.env.LAST_REDELIVERY_VARIABLE_NAME;\n  \n  const WORKFLOW_REPO_NAME = process.env.WORKFLOW_REPO;\n  const WORKFLOW_REPO_OWNER = process.env.WORKFLOW_REPO_OWNER;\n\n  // Create an instance of the octokit `App` using the app ID and private key values that were set in the GitHub Actions workflow.\n  //\n  // This will be used to make API requests to the webhook-related endpoints.\n  const app = new App({\n    appId: APP_ID,\n    privateKey: PRIVATE_KEY,\n  });\n\n  // Create an instance of `Octokit` using the token values that were set in the GitHub Actions workflow.\n  //\n  // This will be used to update the configuration variable that stores the last time that this script ran.\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({lastWebhookRedeliveryTime, app});\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({deliveryId, app});\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 ${Date(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({lastWebhookRedeliveryTime, app}) {\n  const iterator = app.octokit.paginate.iterator(\n    \"GET /app/hook/deliveries\",\n    {\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({deliveryId, app}) {\n  await app.octokit.request(\"POST /app/hook/deliveries/{delivery_id}/attempts\", {\n    delivery_id: deliveryId,\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## Probar el script\n\nPuedes activar manualmente tu flujo de trabajo para probar el script. Para más información, consulta [Ejecutar un flujo de trabajo manualmente](/es/actions/how-tos/manage-workflow-runs/manually-run-a-workflow) y [Uso de registros de flujo de trabajo](/es/actions/how-tos/monitor-workflows/use-workflow-run-logs).\n\n## Métodos alternativos\n\nEn este ejemplo se usa GitHub Actions para almacenar credenciales de forma segura y para ejecutar el script según una programación. Sin embargo, si prefieres ejecutar este script en el servidor que controla las entregas de webhook, puedes hacer lo siguiente:\n\n* Almacene las credenciales de otra manera segura, como un administrador de secretos, como [Azure almacén de claves](https://azure.microsoft.com/products/key-vault). También deberás actualizar el script para acceder a las credenciales desde su nueva ubicación.\n* Ejecuta el script según una programación en el servidor, por ejemplo, mediante un programador de tareas o un trabajo cron.\n* Actualiza el script para almacenar la última hora de ejecución en algún lugar al que el servidor pueda acceder y actualizar. Si decide no almacenar la última hora de ejecución como secreto GitHub Actions , no es necesario usar un personal access tokeny puede quitar las llamadas API para acceder a la variable de configuración y actualizarla."}