skip to content
Burak Berk Keskin
Table of Contents

Overview

In this blog post, we’ll walk you through the process of creating a webhook consumer using Cloudflare Workers. This powerful service allows you to run serverless functions at the edge of the internet, providing fast and scalable solutions without the need for expensive infrastructure.

Cloudflare Workers are JavaScript functions that execute on Cloudflare’s global network. They can respond to HTTP requests, handle webhooks, perform data processing, and much more.

There are two main reasons we chose Cloudflare Workers for this job: it is extremely simple to set up, and it is free to start. The free tier can be used up to a certain limit of requests per day, which is more than enough for a lightweight webhook consumer like this one. If you ever outgrow the free tier, paid plans pick up right where it leaves off.

In this guide, we’ll cover the following steps:

  1. Setting up your Cloudflare account
  2. Creating a new Worker script
  3. Writing the webhook consumer code
  4. Deploying and testing your Worker
  5. Securing your webhook

By the end of this tutorial, you’ll have a fully functional webhook consumer running on Cloudflare Workers. This can be used for various purposes, such as:

  • Receiving notifications from third-party services (e.g., GitHub, Slack)
  • Processing incoming data from IoT devices (e.g., temperature sensors, motion detectors)
  • Integrating with custom applications (e.g., CRM systems, e-commerce platforms)

Step 1: Setting up your Cloudflare account

To begin, you’ll need a Cloudflare account. If you don’t already have one, sign up for the free tier at Cloudflare’s website. The free plan includes everything you need to create and deploy Workers.

Once you’ve registered and logged in, navigate to the “Workers” section of your dashboard. Here, you’ll find options to manage your Worker scripts and view usage statistics.

Step 2: Creating a new Worker script

With your account set up, it’s time to create a new Worker script. In the Workers dashboard, click on the “Create a Worker” button. You’ll be prompted to give your Worker a name and choose a subdomain for it (e.g., mywebhookconsumer.yoursubdomain.workers.dev).

After naming your Worker, you’ll be taken to the editor where you can write your JavaScript code. This is where we’ll implement our webhook consumer logic.

Step 3: Writing the webhook consumer code

Now that we have a new Worker script ready, let’s write the code for our webhook consumer. We’ll use a simple example of receiving notifications from GitHub webhooks.

Here’s a basic implementation:

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
if (request.method === 'POST') {
const payload = await request.json()
// Process the webhook payload here
console.log('Received GitHub webhook:', payload)
return new Response('Webhook received', { status: 200 })
} else {
return new Response('Method not allowed', { status: 405 })
}
}

In this example, we’re listening for HTTP POST requests. When a request is received, we parse the JSON payload and log it to the console. You can replace the console.log statement with any processing logic you need for your specific use case.

Step 4: Deploying and testing your Worker

Once you’ve written your code, click the “Save and Deploy” button in the Cloudflare Workers editor. This will deploy your Worker to the global Cloudflare network.

To test your webhook consumer, you can use a tool like Postman or cURL. Send a POST request to your Worker’s URL with a JSON payload that mimics what you expect from your webhook source (e.g., GitHub).

For example, using cURL:

Terminal window
curl -X POST https://mywebhookconsumer.yoursubdomain.workers.dev \
-H "Content-Type: application/json" \
-d '{
"action": "opened",
"number": 1,
"pull_request": {
"title": "New feature"
}
}'

If everything is set up correctly, you should receive a response from your Worker indicating that the webhook was received.

Step 5: Securing your webhook

By default, anyone who knows your Worker’s URL can send a request to it. For a webhook endpoint, that is usually not acceptable. Three simple measures close the most common gaps: accepting requests only from trusted IP addresses, requiring a shared secret in every request, and verifying the request signature (HMAC) when the provider supports it.

Allowing only trusted IP addresses with Cloudflare

If you know which IP addresses your webhook source sends from, you can block everything else right at the edge with a Cloudflare WAF rule.

  1. In the Cloudflare dashboard, select the zone where your Worker runs.

  2. Navigate to Security → WAF → Custom rules.

  3. Create a new Block rule with an expression that matches your Worker’s hostname and any source IP that is not on your allow list, for example:

    (http.host eq "mywebhookconsumer.yoursubdomain.workers.dev" or http.host eq "webhooks.example.com")
    and not (ip.src in {198.51.100.0/24})

    Replace the CIDR block(s) inside ip.src in { ... } with the actual IP addresses or ranges of your trusted sources. Requests that match the rule are dropped before they ever reach your Worker.

Note: Some services, such as GitHub, serve webhooks from a pool of IPs that can change over time. If that applies to you, fetch the current list from the provider (for GitHub, https://api.github.com/meta returns the ip_allow_list) and keep your rule updated — or rely on the shared secret alone instead.

Requiring a shared secret

The second layer lives inside the Worker itself: every request must carry a secret value that only you and the webhook source know. Many services let you attach a static header to their webhooks (for example, an Authorization: Bearer <token> header). The Worker simply rejects anything that does not match.

const WEBHOOK_SECRET = "my-secret-token" // replace with a strong, random value
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
if (request.method === 'POST') {
if (request.headers.get('Authorization') !== `Bearer ${WEBHOOK_SECRET}`) {
return new Response('Unauthorized', { status: 401 })
}
const payload = await request.json()
console.log('Received webhook:', payload)
return new Response('Webhook received', { status: 200 })
} else {
return new Response('Method not allowed', { status: 405 })
}
}

In production, do not hard-code the secret in your source code. Store it as a Worker secret instead — either in the dashboard under Settings → Variables and Secrets, or with the CLI:

Terminal window
npx wrangler secret put WEBHOOK_SECRET

and read it at runtime with env.WEBHOOK_SECRET from your Worker’s bindings.

To test the protected endpoint, add the header to your cURL call:

Terminal window
curl -X POST https://mywebhookconsumer.yoursubdomain.workers.dev \
-H "Content-Type: application/json" \
-H "Authorization: Bearer my-secret-token" \
-d '{
"action": "opened",
"number": 1,
"pull_request": {
"title": "New feature"
}
}'

Without the header, the Worker answers with 401 Unauthorized; with a matching header and a trusted source IP, you get the usual Webhook received response.

Verifying the request signature (HMAC)

A shared secret proves the request comes from someone who knows it — but if the secret leaks, an attacker can forge requests. Many webhook providers offer stronger protection: they sign the raw request body with a shared secret using HMAC-SHA256 and send the digest in a header (GitHub uses X-Hub-Signature-256). You can verify that signature on the Worker, which guarantees both the origin and the integrity of the payload.

On your side, the general idea is simple. When a request arrives, you compute the same HMAC digest over its raw body using the shared secret and compare it with the digest the provider sent. If they match, the request was signed by someone holding the secret and the body has not been altered in transit. If they do not match — or the signature header is missing — you reject the request, typically with a 401 response.

One important detail: the signature is computed over the raw body bytes. So you must verify it before parsing the JSON — parsing and re-serializing would change the bytes and break the comparison.

As before, the shared secret should be stored as a Worker secret rather than hard-coded in your source code.

Conclusion

By following these steps, you’ve created a basic webhook consumer using Cloudflare Workers. This setup can be expanded and customized to suit various applications, from receiving notifications from third-party services to processing data from IoT devices.

Cloudflare Workers offer a powerful and cost-effective way to handle webhooks and other serverless tasks at the edge of the internet. Combine the IP allow list, the shared secret, and (where available) HMAC signature verification, and you have a webhook endpoint that is hard to fake and hard to tamper with. Whether you’re a seasoned developer or just starting out, this guide should provide a solid foundation for building your own webhook consumers with Cloudflare Workers.