---
title: "Develop webhooks locally using Cloudflared Tunnel"
excerpt: "Webhooks are a common way to integrate with external service, including Twilio!\nLearn how to develop webhooks locally using Cloudflare Tunnels!"
publishDate: "2022-01-28T00:00:00"
createDate: "2022-01-29T01:59:51.22"
updateDate: "2022-01-29T07:03:50.703"
category: "web"
categoryName: "Web"
tags: ["Webhooks","Cloudflare","Twilio","Web"]
permalink: "/blog/web/develop-webhooks-locally-using-cloudflared-tunnel"
canonical: "https://www.twilio.com/en-us/blog/developers/tutorials/building-blocks/develop-webhooks-locally-using-cloudflared-tunnel"
image: {"src":"/media/sxqfins5/develop-webhooks-locally-using-cloudflared-tunnel-canonical.png","focalPoint":{"left":0.5,"top":0.5},"crops":[{"alias":"Banner","width":1120,"height":400,"coordinates":{"x1":0,"y1":0.15393950133279113,"x2":0,"y2":0.1548162590358725}}]}
imageAlt: "Develop webhooks locally using Cloudflared Tunnel"
imageBackgroundColor: "#fff"
thumbnailImage: {"src":"/media/ygnfku2u/cf-logo-v.svg","focalPoint":{"left":0.5,"top":0.5},"crops":[{"alias":"thumbnail","width":250,"height":250,"coordinates":null}]}
thumbnailImageAlt: "Cloudflare logo"
metaTitle: "Develop webhooks locally using Cloudflared Tunnel"
metaDescription: "Webhooks are a common way to integrate with external service, including Twilio!\nLearn how to develop webhooks locally using Cloudflare Tunnels!"
ogTitle: "Develop webhooks locally using Cloudflared Tunnel"
ogDescription: "Webhooks are a common way to integrate with external service, including Twilio!\nLearn how to develop webhooks locally using Cloudflare Tunnels!"
ogImage: {"src":"/media/qffomula/develop-webhooks-locally-using-cloudflared-tunnel-canonical.png","focalPoint":{"left":0.5,"top":0.5},"crops":[{"alias":"OpenGraph","width":1200,"height":630,"coordinates":null}]}
ogImageAlt: "Develop webhooks locally using Cloudflared Tunnel"
relatedPostKeys: ["b9addb92f2604ee7b876afe6a5c11bdf","9cf8ccbb7b514682884b959e9d2e42df","1710543d71f748eba82e9c6891718211","99831c3f1722477fa6329b6a946bd961"]
noIndex: false
noFollow: false
sitemapExclude: true
source: {"nodeId":1581,"contentKey":"5352c97778f64df68477e6a0715df6c0","sortOrder":122}
---


**This blog post was written for [Twilio](https://www.twilio.com/) and originally published at [the Twilio blog](https://www.twilio.com/blog/develop-webhooks-locally-using-cloudflared-tunnel).**


Webhooks are a way to be notified by an external service when an event has occurred. Instead of you sending an HTTP request to that service, the service sends an HTTP request to your public web service. This way, you can respond to the event in real-time as it happens. Webhooks are also a common way to integrate with Twilio's products. For example, when your Twilio phone number receives a text message or phone call, Twilio sends an HTTP request to your service with the details. Your service then responds with instructions that indicate how Twilio should respond to the event. Here's a diagram of what this  exchange looks like:

<p><img class="richtext-image left" src="https://twilio-cms-prod.s3.amazonaws.com/images/nExpal_HHczr1wPXhB0aae0rJ91L105u3jQ1P7i0jEox9d.width-500.png" alt="Diagram showing how SMS messages interact with Twilio and your application using webhooks" width="500" height="281"></p>




One common challenge with webhooks is that they can only call web services that are publicly available on the internet, but when you are initially developing software, you are typically doing so on your own local machine, which by default is not reachable via the internet. So, how do you develop and test webhooks on your local machine? The answer: **tunnels**. In this post, you'll learn how to use the Cloudflare Tunnels service to make your local web server publicly accessible on the internet. Then you'll learn how to use the tunnel service to respond to a Twilio webhook to respond to a phone call.




## Local tunnels {#local-tunnels}

You can create a tunnel between your local machine and a tunnel service to expose local ports to the public internet. There are [many tools](https://github.com/anderspitman/awesome-tunneling) that can help you do this, and today you're going to learn about [Cloudflare Tunnel or cloudflared](https://github.com/cloudflare/cloudflared) (formerly known as Argo Tunnel).

## Prerequisites {#prerequisites}

You will need the following things to follow along:

- A Linux, macOS, or Windows machine
- Your development stack of choice (Node.js, PHP, .NET, Java, Python, etc.)
- <strong>Optional:</strong> A Cloudflare account with a website that uses Cloudflare DNS as their name server
- <strong>Optional: </strong>A Twilio Account (If you <a href="https://www.twilio.com/referral/OhKZOM">register here</a>, you'll receive $10 in Twilio credit when you upgrade to a paid account!)



## Set up a local web server {#set-up-a-local-web-server}



<p>You can tunnel a local port at any time, but you won't see any results if nothing is listening to that port on your machine. In this section, you'll set up a local web server for serving static files. </p>



<p class="panel panel--info">Usually, the response for a webhook is created programmatically, but for the purposes of this demo, you'll use static files.</p>

Open your preferred shell, create a folder, and navigate to it:

<pre style="color:#000000;background:#ffffff;">mkdir MyStaticSite&#10;<span style="color:#7f0055; font-weight:bold; ">cd</span> MyStaticSite&#10;</pre>

Create a new file *index.html* in the *MyStaticSite* folder with the following content:

<pre style="color:#000000;background:#ffffff;"><span style="color:#7f0055; ">&lt;!DOCTYPE html&gt;</span>&#10;<span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; font-weight:bold; ">html</span> lang=<span style="color:#2a00ff; ">"en"</span><span style="color:#7f0055; ">&gt;</span>&#10;<span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; font-weight:bold; ">head</span><span style="color:#7f0055; ">&gt;</span>&#10;    <span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; font-weight:bold; ">meta</span> charset=<span style="color:#2a00ff; ">"UTF-8"</span><span style="color:#7f0055; ">&gt;</span>&#10;    <span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; font-weight:bold; ">meta</span> http-equiv=<span style="color:#2a00ff; ">"X-UA-Compatible"</span> content=<span style="color:#2a00ff; ">"IE=edge"</span><span style="color:#7f0055; ">&gt;</span>&#10;    <span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; font-weight:bold; ">meta</span> name=<span style="color:#2a00ff; ">"viewport"</span> content=<span style="color:#2a00ff; ">"width=device-width, initial-scale=1.0"</span><span style="color:#7f0055; ">&gt;</span>&#10;    <span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; font-weight:bold; ">title</span><span style="color:#7f0055; ">&gt;</span>Hello World!<span style="color:#7f0055; ">&lt;/</span><span style="color:#7f0055; font-weight:bold; ">title</span><span style="color:#7f0055; ">&gt;</span>&#10;<span style="color:#7f0055; ">&lt;/</span><span style="color:#7f0055; font-weight:bold; ">head</span><span style="color:#7f0055; ">&gt;</span>&#10;<span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; font-weight:bold; ">body</span><span style="color:#7f0055; ">&gt;</span>&#10;    Hello World!&#10;<span style="color:#7f0055; ">&lt;/</span><span style="color:#7f0055; font-weight:bold; ">body</span><span style="color:#7f0055; ">&gt;</span>&#10;<span style="color:#7f0055; ">&lt;/</span><span style="color:#7f0055; font-weight:bold; ">html</span><span style="color:#7f0055; ">&gt;</span>&#10;</pre>



<p>You'll need to run a static web server to serve this HTML file. There are many static servers for every development stack out there. Pick your preferred stack below and follow the instructions to serve the current folder at <em>http://localhost:8080/</em>.</p>




**Node.js**: Run the following command to execute [the http-server NPM](https://github.com/http-party/http-server) package and run it:




<pre style="color:#000000;background:#ffffff;">npx http-server <span style="color:#7f0055; font-weight:bold; ">.</span> --port 8080&#10;</pre>



<p><strong>PHP</strong>: You can use <a href="https://www.php.net/manual/en/features.commandline.webserver.php">the built-in PHP web server</a> by running the following command: </p>



<pre style="color:#000000;background:#ffffff;">php -S localhost:8080&#10;</pre>

**.NET:** You can use the [dotnet-serve](https://github.com/natemcmaster/dotnet-serve) tool to run a static web server. Install the tool as a global .NET tool:

<pre style="color:#000000;background:#ffffff;">dotnet tool install --global dotnet-serve&#10;</pre>

Run the tool to serve the current directory:

<pre style="color:#000000;background:#ffffff;">dotnet serve --port 8080&#10;</pre>




**Python 2**:




<pre style="color:#000000;background:#ffffff;">python -m SimpleHTTPServer 8080&#10;</pre>




**Python 3**:




<pre style="color:#000000;background:#ffffff;">python -m http<span style="color:#7f0055; font-weight:bold; ">.</span>server 8080&#10;</pre>

Check out [this list of static web server commands](https://gist.github.com/willurd/5720255) if your preferred programming language is not listed above.

<p class="panel panel--warning"><strong>Leave the static server running and open a new shell for the upcoming commands.</strong></p>



## Tunnel your local web server using Cloudflare Tunnel {#tunnel-your-local-web-server-using-cloudflare-tunnel}

You can use the ​​Cloudflare Tunnel client to tunnel your local ports to the public internet.


You'll need to use the `cloudflared` CLI tool for this. [Download the cloudflared command line tool here](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation).


The quickest way to start tunneling your local machine is to run this command:

<pre style="color:#000000;background:#ffffff;">cloudflared tunnel --url http:<span style="color:#3f3fbf; ">/</span><span style="color:#3f3fbf; ">/localhost</span>:8080&#10;</pre>



<p>This command will tunnel <em>http://localhost:8080</em> to a randomly generated <em>trycloudflare.com</em> subdomain. For example, when I ran this command, the publicly available address was <em>https://</em><strong><em>colleagues-ga-eternal-recruitment</em></strong><em>.trycloudflare.com</em>.</p>

Here's what the output should look like:

<pre style="color:#000000;background:#ffffff;">Thank you for trying Cloudflare Tunnel. Doing so, without a Cloudflare account, is a quick way to experiment and try it out. However, be aware that these account-less Tunnels have no uptime guarantee. If you intend to use Tunnels in production you should use a pre-created named tunnel by following: <span style="color:#3f3fbf; ">https://developers.cloudflare.com/cloudflare-one/connections/connect-apps</span>&#10;Requesting new quick Tunnel on trycloudflare.com...&#10;+--------------------------------------------------------------------------------------------+&#10;|  Your quick Tunnel has been created! Visit it at (it may take some time to be reachable):  |&#10;|  <span style="color:#3f3fbf; ">https://colleagues-ga-eternal-recruitment.trycloudflare.com</span>                               |&#10;+--------------------------------------------------------------------------------------------+&#10;... Much more!&#10;</pre>

You can find your randomly generated address inside the box printed to the console. Go to the address using a web browser. You'll see the HTML file that is served from your local web server is now publicly served at the address Cloudflare gave you.

You can use this public address and configure it as your webhook address with the external services you want to integrate with.

## Use a persistent address for your Cloudflare Tunnel {#use-a-persistent-address-for-your-cloudflare-tunnel}

When you stop the tunnel command, the tunnel will be destroyed. When you run the tunnel command again, a new tunnel with a new random URL will be created. This also means you have to keep updating the webhook URL at your external service, which can be a hassle. Some of these tunnel services do offer persistent URLs as part of their paid plan. But with Cloudflare Tunnels, you can have persistent URLs for free if you use Cloudflare DNS. Instead of using a randomly generated URL, you can create a subdomain in Cloudflare DNS and use that as your persistent tunnel URL.

If you choose to use a persistent URL, you'll need to

1. <a href="https://support.cloudflare.com/hc/en-us/articles/201720164-Creating-a-Cloudflare-account-and-adding-a-website">Create a Cloudflare account and add your website</a>
1. Verify you own your domain name
1. Use <a href="https://support.cloudflare.com/hc/en-us/articles/205195708">Cloudflare DNS as your domain name servers</a>

Feel free to skip this section If you don't already use Cloudflare DNS for your website and you don't want to. You can keep using the randomly generated address for the next section.

Log into Cloudflare using the following command:

<pre style="color:#000000;background:#ffffff;">cloudflared tunnel login&#10;</pre>

This will open Cloudflare's login page. After logging in, select the domain you want to use for tunneling.


![A list of domain names you have in Cloudflare DNS. You are asked to click one to authorize to use the domain for tunneling.](https://twilio-cms-prod.s3.amazonaws.com/images/U9TdWboPx4eJazNSKG2BKz_OndY3jf45BW-N5GsDOdE32G.width-500.png)





When prompted whether you want to authorize this domain, click **Authorize**.


Switch back to your shell, and you should see "You have successfully logged in." in the output.<br><br>Create a new tunnel by running this command:

<pre style="color:#000000;background:#ffffff;">cloudflared tunnel create myfirsttunnel&#10;</pre>




You created a tunnel with name `myfirsttunnel`. You can list all your tunnels using the following command:




<pre style="color:#000000;background:#ffffff;">cloudflared tunnel list&#10;</pre>




Run the above command and **take note of the ID of your tunnel**.




<p>By now, the CLI tool has created a folder to store its configuration. On Windows, this is located at <em>%USERPROFILE%\.cloudflared</em>. On Linux and macOS, you can find this folder at <em>~/.cloudflared</em>. Create a new file <em>myfirsttunnel.yaml</em> in the <em>.cloudflared </em>folder and paste in the following content:</p>



<pre style="color:#000000;background:#ffffff;">url: <span style="color:#3f3fbf; ">http://localhost:8080</span>&#10;tunnel: &lt;Tunnel-UUID&gt;&#10;credentials-file: /root/.cloudflared/&lt;Tunnel-UUID&gt;.json&#10;</pre>




This configuration file will configure the local tunnel to listen to [*http://localhost:8080*](http://localhost:8080).Replace `<Tunnel-UUID>` with the tunnel ID you took note of earlier.


Run the following command to specify the subdomain you want to use for your public tunnel URL:

<pre style="color:#000000;background:#ffffff;">cloudflared tunnel route dns myfirsttunnel mysubdomain&#10;</pre>

In this command, `myfirsttunnel` specifies the name of the tunnel, and `mysubdomain` specifies the subdomain you want to use.

Now that everything is configured, you can run your tunnel using this command:

<pre style="color:#000000;background:#ffffff;">cloudflared tunnel run myfirsttunnel&#10;</pre>



<p>Your new tunnel URL should be: <em>https://&lt;your-subdomain&gt;.&lt;your-domain&gt;.&lt;your-tld&gt;</em>. For example, mine would be <em>https://mysubdomain.swimburger.net</em>.</p>

Open your new tunnel URL in a browser to verify that the same HTML file is being returned.

## Use your tunnel with Twilio webhooks {#use-your-tunnel-with-twilio-webhooks}

You can also use these tunnels to respond to Twilio webhooks. In this section, you'll learn how to respond to a phone call using a static XML file.

Create a new file *call.xml* in the *MyStaticSite* folder:

<pre style="color:#000000;background:#ffffff;"><span style="color:#7f0055; ">&lt;?</span><span style="color:#7f0055; font-weight:bold; ">xml</span><span style="color:#7f0055; "> </span><span style="color:#7f0055; ">version</span><span style="color:#7f0055; ">=</span><span style="color:#2a00ff; ">"</span><span style="color:#7f0055; font-weight:bold; ">1.0</span><span style="color:#2a00ff; ">"</span><span style="color:#7f0055; "> </span><span style="color:#7f0055; ">encoding</span><span style="color:#7f0055; ">=</span><span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">UTF-8</span><span style="color:#2a00ff; ">"</span><span style="color:#7f0055; ">?&gt;</span>&#10;<span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; ">Response</span><span style="color:#7f0055; ">&gt;</span>&#10;  <span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; ">Say</span> voice=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">alice</span><span style="color:#2a00ff; ">"</span><span style="color:#7f0055; ">&gt;</span>Twilio received these instructions from your local machine via Cloudflare Tunnel.<span style="color:#7f0055; ">&lt;/</span><span style="color:#7f0055; ">Say</span><span style="color:#7f0055; ">&gt;</span>&#10;  <span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; ">Pause</span> length=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">1</span><span style="color:#2a00ff; ">"</span> <span style="color:#7f0055; ">/&gt;</span>&#10;  <span style="color:#7f0055; ">&lt;</span><span style="color:#7f0055; ">Say</span> voice=<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">alice</span><span style="color:#2a00ff; ">"</span><span style="color:#7f0055; ">&gt;</span>Great job!<span style="color:#7f0055; ">&lt;/</span><span style="color:#7f0055; ">Say</span><span style="color:#7f0055; ">&gt;</span>&#10;<span style="color:#7f0055; ">&lt;/</span><span style="color:#7f0055; ">Response</span><span style="color:#7f0055; ">&gt;</span>&#10;</pre>




This XML file [uses TwiML, also known as the Twilio Markup Language](https://www.twilio.com/docs/glossary/what-is-twilio-markup-language-twiml). Using TwiML, you can provide instructions on how Twilio should respond to an incoming call or SMS. The TwiML above uses two [TwiML Voice](https://www.twilio.com/docs/voice/twiml) verbs: `Say` and `Pause`. `Say` converts text to speech, and the `voice` attribute specifies which voice to use. `Pause` will wait the specified amount of time, which is one second in this case, and then move on to the next verb.





Make sure your static web server is still running, and run the Cloudflare tunnel using `cloudflared`.<br>Browse to the XML file using the publicly tunneled URL: *\[tunnel-url\]/call.xml*


The XML should be returned to your browser. Take note of this URL.


If you don't already have a Twilio account, you can get a free one. (If you [register here](https://www.twilio.com/referral/OhKZOM), you'll receive $10 in Twilio credit when you upgrade to a paid account!)





Next, you'll need a Twilio phone number. Go and [buy a new phone number from Twilio](https://support.twilio.com/hc/en-us/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console). The cost of the phone number will be applied to your free promotional credit.





Use the left side navigation to navigate to Phone Numbers > Manage > Active numbers. Click on your Twilio phone number to navigate to its settings.




<p><img class="richtext-image left" src="https://twilio-cms-prod.s3.amazonaws.com/images/Kq4XnRiwFeGBPMvBkWg7PzmnBdj-CJ3N5eEn0Tn8rsFij-.width-500.png" alt="Twilio Console page listing active phone numbers in your Twilio account. Cursor is clicking on the single phone number in the list." width="500" height="261"></p>



<p>Find the <strong>A CALL COMES IN</strong> fields under the<strong> Voice &amp; Fax</strong> section. Change the text field to your public tunnel URL with the <em>/call.xml</em> path, and change the HTTP verb to <strong>HTTP GET</strong>.</p>



<p><img class="richtext-image left" src="https://twilio-cms-prod.s3.amazonaws.com/images/jOaqQcCwTu1Dzb28ZVqII7JWNcXyhMo0NPnNiDERnNoQXV.width-500.png" alt="Twilio phone number settings screen. The fields on the form configure the phone number webhook to send HTTP GET requests to https://mydomain.swimburger.net/call.xml" width="500" height="300"></p>

Click **Save** to submit the form.

To verify the call is working as expected, call your Twilio phone number using your personal phone. You should hear "Twilio received these instructions from your local machine via Cloudflare Tunnel. Great job!".

Using a static file is sufficient to demonstrate local tunneling, but you can also use the same techniques in combination with any web server technology to process and respond to the HTTP request.

## Developing webhooks locally with Cloudflared tunnel {#developing-webhooks-locally-with-cloudflared-tunnel}

Webhooks are a great way to be notified by an external service when an event has occurred. The external service makes an HTTP request to your public web service and provides the details of the event in the body of the request. You can use local tunneling to make your local port accessible on the internet using a tunnel service like Cloudflare Tunnels. Using Cloudflare Tunnels, you can tunnel your local port to a random public URL provided by Cloudflare. If you use Cloudflare DNS, you can also configure a persistent tunnel URL for free.

<p>Many Twilio products have webhook capabilities. Learn how you can infuse communication technology into your application in the <a href="https://www.twilio.com/docs">Twilio Docs</a>, and learn how to get started quickly with <a href="https://www.twilio.com/docs/libraries">Twilio's SDKs</a>. </p>
