Tooling for rotating the GitHub webhook secret on CircleCI GitHub OAuth project triggers. Background: CircleCI support article — Rotating the GitHub webhook secret for CircleCI GitHub OAuth project triggers.
curl,jq- A CircleCI personal API token — API Developers Guide
export CIRCLE_TOKEN=<your-personal-api-token>Only projects using the GitHub OAuth integration need rotation. Projects already on the newer GitHub App integration are not affected.
The v1.1 API returns all projects your token follows:
curl -sS \
-H "Circle-Token: $CIRCLE_TOKEN" \
"https://circleci.com/api/v1.1/projects" \
| jq -r '.[] | select(.vcs_type == "github") | "gh/\(.username)/\(.reponame)"' \
> slugs.txtNote: this only returns projects you follow. For org-wide coverage with an admin token, paginate
GET /api/v2/projects?org-slug=gh/<org>instead.
Use DRY_RUN=1 to check which projects have an OAuth trigger without modifying
anything. Projects that print a Trigger id: line need rotation; projects that
print no pipeline definition or no github_oauth trigger are already on the
GitHub App integration and can be skipped.
while IFS= read -r slug; do
echo "--- $slug"
CIRCLE_TOKEN="$CIRCLE_TOKEN" \
CIRCLECI_PROJECT_SLUG="$slug" \
DRY_RUN=1 \
./rotate.sh 2>&1 | grep -E '(definition id|Trigger id|no pipeline definition|no github_oauth)'
done < slugs.txtexport CIRCLECI_PROJECT_SLUG=gh/your-org/your-repo
./rotate.shOptional overrides:
| Variable | Default | Description |
|---|---|---|
PIPELINE_DEFINITION_ID |
first github_oauth definition |
explicit pipeline definition UUID |
TRIGGER_ID |
sole OAuth trigger on the definition | required if there are multiple OAuth triggers |
DRY_RUN |
unset | set to any non-empty value to print actions without making changes |
CIRCLECI_API_ROOT |
https://circleci.com/api/v2 |
override for on-prem / proxy |
To rotate all projects identified in the audit step:
while IFS= read -r slug; do
echo "--- rotating $slug"
CIRCLE_TOKEN="$CIRCLE_TOKEN" \
CIRCLECI_PROJECT_SLUG="$slug" \
./rotate.sh
done < slugs.txt 2>&1 | tee output.logrotate.sh sends diagnostic output (project id, trigger id, recreate body) to
stderr and the final JSON response to stdout. Merging with 2>&1 before tee
ensures both end up in the log. tee also prints to the terminal so you can
follow progress as it runs.
rotate.sh performs a delete-and-recreate of the GitHub OAuth trigger, which
has the same effect as running Project Setup in the CircleCI UI: the old GitHub
webhook is removed and a new one with a fresh secret is registered.
The script:
- Resolves the human project slug (
gh/org/repo) to an internal project UUID. - Finds the pipeline definition with
config_source.provider == "github_oauth". - Finds the trigger with
event_source.provider == "github_oauth"on that definition. - Fetches the full trigger payload and strips read-only fields to build a recreate request.
DELETEs the trigger, thenPOSTs a new one.
Both the pipeline definition ID and trigger ID are stable across rotation — CircleCI reassigns the same UUIDs when the trigger is recreated for the same repo and definition. Any stored references to these IDs remain valid after rotation.
API reference: CircleCI OpenAPI spec