GitHub webhooks allow applications to receive notifications when events occur in a repository. For example, a webhook can notify a deployment service when code is pushed, trigger an automated test when a pull request is opened, or update an internal system when an issue changes.
Many organizations protect webhook endpoints with firewalls, reverse proxies, or network access control rules. These controls can restrict incoming connections to approved IP addresses. However, problems arise when GitHub changes the IP addresses used for webhook delivery and the firewall continues to allow only an outdated list.
The result can be missed notifications, failed automation, and delays in development workflows.
The solution is not to disable network restrictions or manually update firewall rules whenever a delivery fails. Instead, developers should establish a reliable process for retrieving GitHub's published IP ranges, updating the appropriate allowlists, and verifying that webhook delivery continues to work.
This article explains how GitHub webhook IP addresses work, how to maintain firewall rules, and how to troubleshoot delivery failures without weakening security.
Why GitHub Webhook IP Addresses Matter
When an event occurs in a GitHub repository, GitHub sends an HTTP request to the configured webhook URL. The receiving application processes that request and performs the required action.
A simplified flow looks like this:
GitHub Repository Event
|
v
GitHub Webhook Delivery
|
v
Firewall or Reverse Proxy
|
v
Webhook Endpoint
|
v
Application Processes EventIf the firewall rejects the incoming connection, the application may never receive the request.
For example, suppose a company allows traffic from a documented set of GitHub IP addresses. If GitHub begins delivering webhook requests from an address that is not included in the allowlist, the firewall may block those requests.
The application can be healthy, and the webhook configuration can be correct, yet delivery still fails.
Maintaining an accurate allowlist helps prevent this problem while preserving network restrictions.
Understanding GitHub's Published IP Ranges
GitHub publishes network information through its metadata API. Developers can use this information to identify the IP ranges associated with supported GitHub services.
The metadata response includes categories such as hooks, web, and api, along with other service-related information.
For webhook firewall rules, the hooks category is the relevant starting point.
The following example illustrates the structure of a response:
{
"hooks": [
"192.0.2.0/24",
"198.51.100.0/24"
]
}The addresses in this example are reserved for documentation. They are not actual GitHub webhook ranges.
The metadata API returns CIDR notation, which represents an IP address range rather than a single address. For example, an IPv4 CIDR range such as 192.0.2.0/24 represents a network containing multiple addresses.
A firewall or cloud network rule may support CIDR ranges directly. If it does, storing the documented ranges can be simpler than generating a separate rule for every address.
However, the supported range format and maximum number of rules depend on the firewall or network platform.
Step 1: Retrieve the Current Webhook IP Ranges
Use GitHub's metadata API to retrieve the current published information.
A simple command-line request can be made with curl:
curl --fail --silent --show-error \
https://api.github.com/metaThe response contains multiple categories. Avoid copying the entire response into a firewall configuration because not every category represents webhook delivery traffic.
You can extract the hooks field using jq:
curl --fail --silent --show-error \
https://api.github.com/meta |
jq -r '.hooks[]'This command prints the published webhook-related ranges, one per line.
For an automated process, also check the HTTP status code and confirm that the expected JSON structure exists. A failed request, rate limit response, or unexpected payload should not cause the existing firewall rules to be replaced with an empty list.
Do not treat an unsuccessful API request as evidence that GitHub has removed its webhook IP ranges.
Step 2: Compare the Published Ranges With Existing Rules
Before changing firewall rules, compare the latest published ranges with the current allowlist.
A useful update process should identify three groups:
Change | Meaning | Recommended action |
|---|---|---|
New range | A published range is missing from the current rules | Add the range after validation |
Removed range | A previously configured range is no longer published | Remove it according to the approved update policy |
Unchanged range | The range exists in both lists | Keep the existing rule |
Do not remove existing rules until the new metadata has been retrieved and validated successfully.
This distinction is important because firewall updates are security-sensitive. A parsing error or temporary network failure should not leave the webhook endpoint without its intended protection.
In a production environment, save the previous allowlist before applying an update. This provides a reference for reviewing changes and recovering from an incorrect configuration.
Step 3: Automate the Update Process
Manually checking GitHub's metadata is reasonable for a small test environment. It becomes less reliable when multiple firewalls, cloud environments, and deployment pipelines depend on the same information.
A small automation script can retrieve the metadata and produce a validated list of ranges.
The following Python example fetches the metadata and checks that the hooks field contains valid IP networks.
import ipaddress
import json
import urllib.error
import urllib.request
METADATA_URL = "https://api.github.com/meta"
def fetch_hook_ranges():
request = urllib.request.Request(
METADATA_URL,
headers={"Accept": "application/vnd.github+json"},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
if response.status != 200:
raise RuntimeError(
f"Unexpected HTTP status: {response.status}"
)
payload = json.load(response)
except (urllib.error.URLError, TimeoutError) as exc:
raise RuntimeError(
"Unable to retrieve GitHub metadata"
) from exc
ranges = payload.get("hooks")
if not isinstance(ranges, list) or not ranges:
raise RuntimeError(
"Missing or empty hooks range list"
)
validated_ranges = []
for value in ranges:
if not isinstance(value, str):
raise RuntimeError("Invalid IP range entry")
network = ipaddress.ip_network(value, strict=False)
validated_ranges.append(str(network))
return sorted(set(validated_ranges))
if __name__ == "__main__":
for network in fetch_hook_ranges():
print(network)
Join the conversation! Your thoughts help the community grow.