Backend Engineering

How to Deploy FastAPI on Cloudflare Workers

Learn how to deploy a FastAPI application on Cloudflare Workers using Python Workers, Neon PostgreSQL, and Cloudflare Hyperdrive. Build, test, and deploy a webhook API step by step.

M
Md Shayon
Nov 3, 2026
30 min read
Table of Contents
How to Deploy FastAPI on Cloudflare Workers

If you've worked with Python, you've probably heard about FastAPI. It's a great framework for building APIs. But when it comes to deployment, you usually need a server or some hosting service to run your application. What if you could deploy your FastAPI application without managing a traditional server? That's where Cloudflare Workers comes in. Cloudflare Workers lets you run your code on Cloudflare's network without setting up and managing a VPS. And yes, you can run Python applications on it too. In this tutorial, we'll deploy a FastAPI application on Cloudflare Workers. But instead of building a basic Hello World API, we'll create something more useful: a Webhook Relay API. Our API will be able to:

  1. Receive webhook events through HTTP requests.

  2. Validate incoming data using Pydantic.

  3. Store webhook events in Neon PostgreSQL.

  4. Connect to PostgreSQL using Cloudflare Hyperdrive.

  5. Forward webhook events to another endpoint.

  6. Retrieve the latest 20 webhook events.

  7. We'll build everything step by step, from setting up the project to deploying it.

By the end, you'll have a working FastAPI application running on Cloudflare Workers. Let's get started.

What Is Cloudflare Workers?

Cloudflare Workers is a platform that lets you run code without managing your own server. Normally, when you build a Python API, you need a server to run it. You install Python, configure your application, start an ASGI server like Uvicorn, and keep everything running. Cloudflare Workers takes a different approach. You write your application, configure it, and deploy it. Cloudflare handles the infrastructure needed to run it.

You don't have to manage:

  • A VPS or dedicated server.

  • Operating system updates.

  • A running Uvicorn process.

  • Manual server scaling.

  • Most of the usual server maintenance.

Cloudflare Workers was originally built around JavaScript and TypeScript. But it also supports Python through Python Workers. Python Workers use Pyodide, which runs Python through WebAssembly. This means you can run supported Python applications and libraries without setting up a traditional Python server. You can even run frameworks like FastAPI.

Read article on "Skills for AI"

Why Deploy FastAPI on Cloudflare Workers?

FastAPI is a popular Python framework for building APIs. It's easy to work with, supports asynchronous functions, and automatically validates request data using Pydantic. But deploying FastAPI usually means choosing a hosting platform and managing its runtime. Cloudflare Workers gives you another option.

You can use it to build and deploy things like:

  • Small REST APIs.

  • Webhook receivers.

  • Backend services.

  • Serverless API endpoints.

  • APIs that communicate with external services.

  • Lightweight applications that need database access.

Cloudflare Workers vs a Traditional VPS

Let's look at the main differences.

Feature

Cloudflare Workers

Traditional VPS

Server management

Managed by Cloudflare

You manage it

Deployment

Wrangler / PyWrangler

Depends on hosting

Python runtime

Pyodide

Native Python

Scaling

Managed by platform

Requires configuration

Application server

Workers ASGI integration

Usually Uvicorn

Database connection

Supported bindings and connections

Direct connection

System access

Limited

More control

So, is Cloudflare Workers always better than a VPS?

Not really. If your application needs special system packages, native Python libraries, long-running processes, or full control over the server, a VPS may be a better choice. But if you're building a lightweight API or webhook service, Cloudflare Workers is worth trying.

Read article on "Stop Ignoring Cloudflare"

How Does FastAPI Work on Cloudflare Workers?

Before writing any code, let's understand how the whole thing works. When you deploy FastAPI normally, you usually run it with an ASGI server like Uvicorn.

For example:

uvicorn main:app --reload

Uvicorn receives HTTP requests and passes them to your FastAPI application. But Cloudflare Workers doesn't run a traditional Uvicorn server.

So, how does FastAPI handle requests there?

The answer is ASGI integration.

What Is ASGI?

ASGI stands for Asynchronous Server Gateway Interface. Don't worry too much about the name. It's basically a standard way for Python web applications and servers to communicate. FastAPI uses ASGI. Cloudflare provides an integration that allows a Python Worker to pass incoming requests to an ASGI application.

We can use it like this:

from workers import asgi

Then connect our FastAPI application using:

return await asgi.fetch(app, request, self.env)

That's the main idea.

We don't need to start Uvicorn. Cloudflare Workers handles the incoming request, and the ASGI integration passes it to FastAPI.

Understanding Our Project Architecture

Let's look at what we're going to build.

              Client / Browser
                     |
                     | HTTP Request
                     v
             Cloudflare Workers
                     |
                     v
                FastAPI App
                     |
              +------+------+
              |             |
              v             v
        Cloudflare       HTTPX
         Hyperdrive        Client
              |             |
              v             v
        Neon PostgreSQL  External API

Here's what happens when someone sends a webhook:

  1. The client sends a request to our Cloudflare Worker.

  2. Cloudflare passes the request to our Python application.

  3. FastAPI receives and validates the request.

  4. The event is saved in Neon PostgreSQL through Hyperdrive.

  5. If a destination URL is configured, HTTPX forwards the event.

  6. The API returns a response to the client. That's our complete request flow.

Read article on "Resume Not Getting Interviews? Here's Why"

Technologies We Will Use

We'll use a few tools to build this project.

Technology

What we'll use it for

Python

Main programming language

FastAPI

Building API endpoints

Cloudflare Workers

Running our application

Pyodide

Running Python through WebAssembly

PyWrangler

Running and deploying Python Workers

Wrangler

Managing Cloudflare configuration

Neon PostgreSQL

Storing webhook events

Cloudflare Hyperdrive

Connecting to PostgreSQL

asyncpg

Talking to PostgreSQL

HTTPX

Sending HTTP requests

Pydantic

Validating incoming data

uv

Managing Python packages

You don't need to know everything about these tools before starting. I'll explain the important parts as we use them.

Read article on: "Boost Developer Energy on Hard Days: 7 Proven Techniques"

Prerequisites

Before we start, make sure you have these tools installed.

1. Python

You'll need a Python version supported by your Python Workers setup.

Cloudflare's current FastAPI setup uses Python 3.13 or later.

Check your version:

python --version

2. Node.js and npm

We'll use npm to install Wrangler.

Check whether they're installed:

node --version
npm --version

3. uv

We'll use uv to install Python packages and run PyWrangler.

Check your installation:

uv --version

If you don't have it, follow the official installation guide.

4. Cloudflare Account

Create a free account at: https://dash.cloudflare.com/ We'll use it to create a Worker and configure Hyperdrive.

5. Neon PostgreSQL Account

We'll use Neon for our database. Create an account and a new PostgreSQL project. We'll need the database connection details later.

Read article on "Choose Between FastAPI and Express.js"

Step 1: Create a Python Workers Project

Let's create our project.

Open your terminal and run:

mkdir fastapi-cloudflare-workers

cd fastapi-cloudflare-workers

Initialize a Python project:

uv init --bare

Now create a source directory:

mkdir src

Create our main Python file:

touch src/entry.py

Your project should look like this:

fastapi-cloudflare-workers/
├── src/
│   └── entry.py
├── pyproject.toml
└── README.md

We'll add a few more files as we go.

Read article on "Why OpenAI Says We Are Entering the AGI Era"

Step 2: Install the Required Dependencies

Our application needs a few Python packages. Open pyproject.toml and add the following:

[project]
name = "fastapi-cloudflare-workers"
version = "0.1.0"
description = "A serverless webhook relay API built with FastAPI and Cloudflare Workers"
requires-python = ">=3.13"
dependencies = [
    "fastapi",
    "httpx",
    "asyncpg",
]

[dependency-groups]
dev = [
    "workers-py",
    "workers-runtime-sdk",
]

Here's what these packages do:

  • fastapi handles our API routes.

  • httpx sends requests to external endpoints.

  • asyncpg connects to PostgreSQL.

  • workers-py provides the Python Workers command-line tools.

  • workers-runtime-sdk provides access to the Cloudflare Workers runtime.

Now install everything:

uv sync

This installs the required packages and creates a uv.lock file.

The lock file keeps track of the exact dependency versions, which helps when someone else runs the project.

Install Wrangler

We also need Wrangler.

Create a package.json file:

{
  "name": "fastapi-cloudflare-workers",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "deploy": "uv run pywrangler deploy",
    "dev": "uv run pywrangler dev",
    "start": "uv run pywrangler dev"
  },
  "devDependencies": {
    "wrangler": "^4.151.0"
  }
}

Now install it:

npm install

That's it. Our basic project setup is ready.

Read article on "Switching From SQL to MongoDB"

Step 3: Create a Neon PostgreSQL Database

Our webhook API needs a database to store incoming events.

For this project, we'll use Neon PostgreSQL.

Go to: https://neon.com/

Create a new project.

Give it a name and choose a region. Once the database is ready, open the connection details.

You'll find a connection string that looks something like this:

postgresql://username:password@hostname/database?sslmode=require

Keep this connection string somewhere safe. We'll need it when creating our Hyperdrive configuration.

Create the Webhook Table

Now open the Neon SQL Editor.

Run this SQL query:

CREATE TABLE IF NOT EXISTS webhook_events (
    id BIGSERIAL PRIMARY KEY,
    event_type VARCHAR(255) NOT NULL,
    payload JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Let's quickly understand these columns.

Column

What it stores

id

Unique ID for each event

event_type

Type of webhook event

payload

Actual event data

created_at

When the event was created

You might be wondering why we're using JSONB for the payload.

Well, webhook data can look different depending on the event.

For example, a user registration event might contain:

{
  "user_id": 123,
  "name": "John"
}

But a payment event might contain:

{
  "payment_id": "PAY-1001",
  "amount": 250
}

Instead of creating different database columns for every possible field, we can store the complete payload as JSONB.

That's exactly what we need here.

Read article on "What to Know Before Learning Next.js"

Step 4: Configure Cloudflare Hyperdrive

Now we need to connect our Neon database to Cloudflare Workers.

This is where Hyperdrive comes in.

What Is Cloudflare Hyperdrive?

Cloudflare Hyperdrive helps Workers connect to existing databases like PostgreSQL.

It manages database connections and can cache eligible queries.

Why does that matter?

A serverless application can handle requests across many Worker instances. Managing database connections in this kind of environment can be tricky.

Hyperdrive helps by managing connections to your database.

We don't need to move our database to Cloudflare. Our data will stay in Neon.

Hyperdrive simply sits between our Worker and PostgreSQL.

Create a Hyperdrive Configuration

First, log in to Cloudflare:

npx wrangler login

Now create a Hyperdrive configuration:

npx wrangler hyperdrive create webhook-database \
  --connection-string="postgresql://USERNAME:PASSWORD@HOSTNAME/DATABASE?sslmode=require"

Replace the example connection string with your actual Neon connection string.

After running the command, Wrangler will return a Hyperdrive ID.

For example:

Hyperdrive config created successfully.

ID: YOUR_HYPERDRIVE_ID

Copy this ID. We'll use it in the next step.

One thing to remember: Hyperdrive doesn't replace PostgreSQL. It helps your Worker connect to the PostgreSQL database you already have.

Also, Cloudflare currently documents Python Workers Hyperdrive support as beta. Keep that in mind if you're planning to use this setup for an important production application.

Official documentation: https://developers.cloudflare.com/hyperdrive/examples/python-workers/

Read article on "Gates Calls for Global AI Rules & Human Jobs"

Step 5: Configure Cloudflare Workers

Now let's tell Cloudflare how to run our application.

Create a file named wrangler.jsonc in the project root.

Add this configuration:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "fastapi-cloudflare-workers",
  "main": "src/entry.py",
  "compatibility_date": "2026-10-06",
  "compatibility_flags": [
    "python_workers"
  ],
  "observability": {
    "enabled": true
  },
  "hyperdrive": [
    {
      "binding": "HYPERDRIVE",
      "id": "YOUR_HYPERDRIVE_ID"
    }
  ],
  "vars": {
    "WEBHOOK_DESTINATION_URL": "https://your-destination.example.com/webhooks"
  }
}

Replace YOUR_HYPERDRIVE_ID with the ID you received earlier.

Let's go through the important parts.

Worker Name

"name": "fastapi-cloudflare-workers"

This is simply the name of our Worker.

Main Entry File

"main": "src/entry.py"

This tells Wrangler where our Python application starts.

Compatibility Date

"compatibility_date": "2026-10-06"

Cloudflare uses this date to decide which runtime features and behavior your Worker should use.

For Python Workers with Hyperdrive, Cloudflare requires a compatibility date of September 8, 2026, or later.

That's why we're using October 6, 2026.

Python Workers Flag

"compatibility_flags": [
  "python_workers"
]

This enables the Python Workers runtime.

Make sure you don't forget this flag.

Hyperdrive Binding

"hyperdrive": [
  {
    "binding": "HYPERDRIVE",
    "id": "YOUR_HYPERDRIVE_ID"
  }
]

This connects our Worker to the Hyperdrive configuration we created earlier.

Notice the binding name:

HYPERDRIVE

We'll use the same name in our Python code.

Webhook Destination URL

"vars": {
  "WEBHOOK_DESTINATION_URL": "https://your-destination.example.com/webhooks"
}

This is the endpoint where our API will forward incoming webhook events.

You can use your own endpoint here.

If you don't need webhook forwarding, you can remove this variable.

One more thing: don't put database passwords or API keys in this file. Use Cloudflare secrets for sensitive information.

Read article on "OpenCode vs Claude Code"

Step 6: Build the FastAPI Application

Now comes the fun part. Let's write some Python code.

Open:

src/entry.py

We'll build the application piece by piece.

6.1 Import the Required Packages

Start with these imports:

from typing import Any
import httpx
import asyncpg
import json

from pydantic import BaseModel, Field
from fastapi import FastAPI, status, HTTPException

from workers import WorkerEntrypoint, asgi

Nothing unusual here.

We're importing FastAPI, Pydantic, HTTPX, asyncpg, and the Cloudflare Workers integration.

Now create the FastAPI application:

app = FastAPI()

That's our application instance.

6.2 Create a Webhook Request Model

When someone sends a webhook, we need to know what data we're expecting.

Let's create a Pydantic model:

class WebhookCreate(BaseModel):
    event_type: str = Field(
        min_length=1,
        max_length=255
    )

    payload: dict[str, Any]

We expect two fields:

  • event_type: The name of the event.

  • payload: The actual event data.

For example:

{
  "event_type": "user.created",
  "payload": {
    "user_id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  }
}

Pydantic checks the incoming data for us.

If event_type is empty or longer than 255 characters, FastAPI rejects the request.

The payload must also be a JSON object.

Pretty simple, right?

6.3 Create a Database Connection Function

Next, we need a function to connect to PostgreSQL.

Add this:

async def get_db_connection():
    hyperdrive = app.state.hyperdrive

    try:
        return await asyncpg.connect(
            host=hyperdrive.host,
            port=int(hyperdrive.port),
            user=hyperdrive.user,
            password=hyperdrive.password,
            database=hyperdrive.database,
            ssl=False,
            timeout=10
        )

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Database connection unavailable"
        ) from e

Let's see what's happening.

First, we get the Hyperdrive binding:

hyperdrive = app.state.hyperdrive

We made this binding available in our Worker configuration.

Next, we use asyncpg.connect() to connect to PostgreSQL.

return await asyncpg.connect(
    host=hyperdrive.host,
    port=int(hyperdrive.port),
    user=hyperdrive.user,
    password=hyperdrive.password,
    database=hyperdrive.database,
    ssl=False,
    timeout=10
)

Notice that we're not writing our database password directly in the Python file.

Hyperdrive provides the connection details.

You might also notice:

ssl=False

This is intentional for the Hyperdrive connection pattern used here. Hyperdrive handles the connection to the upstream database.

Don't blindly copy this setting when connecting directly to Neon. A direct Neon connection should use TLS.

We also set a 10-second connection timeout.

If something goes wrong while connecting, the function returns an HTTP 503 error.

6.4 Create a Function to Format Webhook Events

When we retrieve data from PostgreSQL, we want to return it in a clean JSON format.

Let's create a small helper function:

def serialize_event(row) -> dict:
    payload = row["payload"]

    if isinstance(payload, str):
        payload = json.loads(payload)

    return {
        "id": row["id"],
        "event_type": row["event_type"],
        "payload": payload,
        "created_at": row["created_at"],
    }

This function takes a database row and converts it into a Python dictionary.

It also checks whether the payload is a string. If it is, we convert it into a Python object using json.loads().

We'll use this function in our API endpoints.

6.5 Create the Welcome Endpoint

Let's start with a simple endpoint.

@app.get("/")
async def home():
    return {
        "message": "Welcome to the webhook relay api!",
        "platform": "Cloudflare workers"
    }

When someone visits the root URL, they'll receive a welcome message.

This endpoint doesn't need a database connection.

6.6 Create a Health Check Endpoint

Now add a health check:

@app.get("/health")
async def health():
    return {
        "status": "Ok",
    }

This is useful when you want to check whether your API is responding.

Just remember that this endpoint doesn't check the database connection. It only tells us that the application is responding.

6.7 Create an Endpoint to Get Webhook Events

Now let's retrieve the events we've saved in PostgreSQL.

Add this endpoint:

@app.get("/webhooks")
async def get_webhooks():
    connection = await get_db_connection()

    try:
        rows = await connection.fetch(
            """
            SELECT
                id,
                event_type,
                payload::text AS payload,
                created_at
            FROM webhook_events
            ORDER BY created_at DESC, id DESC
            LIMIT 20
            """
        )

    finally:
        await connection.close()

    webhooks = [serialize_event(row) for row in rows]

    return {
        "count": len(webhooks),
        "webhooks": webhooks,
    }

Here's what we're doing.

First, connect to the database:

connection = await get_db_connection()

Then run a SQL query:

SELECT
    id,
    event_type,
    payload::text AS payload,
    created_at
FROM webhook_events
ORDER BY created_at DESC, id DESC
LIMIT 20;

We're getting the latest 20 webhook events.

Why only 20?

For this tutorial, we want to keep the example simple. Later, you can add pagination to retrieve more records.

After getting the data, we format it:

webhooks = [serialize_event(row) for row in rows]

Finally, we return the result as JSON.

Notice this part:

finally:
    await connection.close()

We close the database connection after the query finishes, even if something goes wrong.

That's important when working with databases.

6.8 Create the Webhook Receiver Endpoint

Now let's build the main feature of our project.

This endpoint will:

  1. Receive a webhook.

  2. Validate the request.

  3. Save the event in PostgreSQL.

  4. Forward it to another endpoint if one is configured.

  5. Return the result.

Add this code:

@app.post("/webhooks", status_code=status.HTTP_201_CREATED)
async def create_webhook(data: WebhookCreate):
    connection = await get_db_connection()

    try:
        row = await connection.fetchrow(
            """
            INSERT INTO webhook_events(
                event_type,
                payload
            )
            VALUES($1, $2::jsonb)
            RETURNING
                id,
                event_type,
                payload::text AS payload,
                created_at
            """,
            data.event_type,
            json.dumps(data.payload)
        )

    finally:
        await connection.close()

    webhook = serialize_event(row)

    destination_url = app.state.webhook_destination

    forwarded = False
    delivery_error = None

    if destination_url:
        try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                response = await client.post(
                    destination_url,
                    json={
                        "event_type": data.event_type,
                        "payload": data.payload,
                    }
                )

                response.raise_for_status()
                forwarded = True

        except httpx.HTTPError:
            delivery_error = "Webhook forwarding failed"

    return {
        "message": "Webhook received successfully",
        "webhook": webhook,
        "delivery": {
            "forwarded": forwarded,
            "error": delivery_error
        }
    }

Let's break it down.

First, save the event in PostgreSQL.

row = await connection.fetchrow(
    """
    INSERT INTO webhook_events(
        event_type,
        payload
    )
    VALUES($1, $2::jsonb)
    RETURNING
        id,
        event_type,
        payload::text AS payload,
        created_at
    """,
    data.event_type,
    json.dumps(data.payload)
)

We insert the event type and payload into the database.

You might notice $1 and $2 in the SQL query.

These are placeholders for the values we're passing to the database driver.

This is better than building SQL queries by joining user input into strings. It also helps protect against SQL injection.

Next, forward the event.

async with httpx.AsyncClient(timeout=10.0) as client:

We're using HTTPX to send an HTTP request.

Then:

response = await client.post(
    destination_url,
    json={
        "event_type": data.event_type,
        "payload": data.payload,
    }
)

This sends the webhook event to our configured destination.

We also call:

response.raise_for_status()

This raises an error if the destination responds with an unsuccessful HTTP status code.

If forwarding works, we set:

forwarded = True

Otherwise, we return an error message.

Finally, return the response.

Our API returns the saved webhook and its delivery status.

One important thing to understand: we save the event before trying to forward it.

So, even if the external endpoint is down, the event stays in PostgreSQL.

That's useful, but our current implementation doesn't retry failed deliveries. We'll look at a better approach later.

Read article on "SvelteKit 3 is here"

Step 7: Connect FastAPI to Cloudflare Workers

Our FastAPI application is almost ready.

But we still need to connect it to the Cloudflare Workers runtime.

Open src/entry.py and add this code at the bottom:

class Default(WorkerEntrypoint):

    async def fetch(self, request):
        app.state.hyperdrive = self.env.HYPERDRIVE

        app.state.webhook_destination = getattr(
            self.env,
            "WEBHOOK_DESTINATION_URL",
            None
        )

        return await asgi.fetch(app, request, self.env)

Let's understand these few lines.

WorkerEntrypoint

class Default(WorkerEntrypoint):

This defines our Python Worker's entrypoint.

When an HTTP request reaches the Worker, Cloudflare calls the fetch() method.

Get the Hyperdrive Binding

app.state.hyperdrive = self.env.HYPERDRIVE

Here, we're getting the Hyperdrive binding from the Worker environment.

The name must match the binding we configured in wrangler.jsonc.

Get the Webhook Destination

app.state.webhook_destination = getattr(
    self.env,
    "WEBHOOK_DESTINATION_URL",
    None
)

This reads the destination URL from our environment variables.

If the variable doesn't exist, it returns None.

That way, our API can still save webhook events without forwarding them.

Pass Requests to FastAPI

Finally:

return await asgi.fetch(app, request, self.env)

This is the important part.

It passes the incoming request to our FastAPI application using Cloudflare's ASGI integration.

That's how FastAPI handles requests inside Cloudflare Workers.

No Uvicorn. No traditional Python server.

Step 8: Complete Python Application

At this point, your src/entry.py should look like this:

from typing import Any
import httpx
import asyncpg
import json

from pydantic import BaseModel, Field
from fastapi import FastAPI, status, HTTPException

from workers import WorkerEntrypoint, asgi


app = FastAPI()


def serialize_event(row) -> dict:
    payload = row["payload"]

    if isinstance(payload, str):
        payload = json.loads(payload)

    return {
        "id": row["id"],
        "event_type": row["event_type"],
        "payload": payload,
        "created_at": row["created_at"],
    }


class WebhookCreate(BaseModel):
    event_type: str = Field(min_length=1, max_length=255)
    payload: dict[str, Any]


async def get_db_connection():
    hyperdrive = app.state.hyperdrive

    try:
        return await asyncpg.connect(
            host=hyperdrive.host,
            port=int(hyperdrive.port),
            user=hyperdrive.user,
            password=hyperdrive.password,
            database=hyperdrive.database,
            ssl=False,
            timeout=10
        )

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Database connection unavailable"
        ) from e


@app.get("/")
async def home():
    return {
        "message": "Welcome to the webhook relay api!",
        "platform": "Cloudflare workers"
    }


@app.get("/health")
async def health():
    return {
        "status": "Ok",
    }


@app.get("/webhooks")
async def get_webhooks():
    connection = await get_db_connection()

    try:
        rows = await connection.fetch(
            """
            SELECT
                id,
                event_type,
                payload::text AS payload,
                created_at
            FROM webhook_events
            ORDER BY created_at DESC, id DESC
            LIMIT 20
            """
        )

    finally:
        await connection.close()

    webhooks = [serialize_event(row) for row in rows]

    return {
        "count": len(webhooks),
        "webhooks": webhooks,
    }


@app.post("/webhooks", status_code=status.HTTP_201_CREATED)
async def create_webhook(data: WebhookCreate):
    connection = await get_db_connection()

    try:
        row = await connection.fetchrow(
            """
            INSERT INTO webhook_events(
                event_type,
                payload
            )
            VALUES($1, $2::jsonb)
            RETURNING
                id,
                event_type,
                payload::text AS payload,
                created_at
            """,
            data.event_type,
            json.dumps(data.payload)
        )

    finally:
        await connection.close()

    webhook = serialize_event(row)

    destination_url = app.state.webhook_destination

    forwarded = False
    delivery_error = None

    if destination_url:
        try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                response = await client.post(
                    destination_url,
                    json={
                        "event_type": data.event_type,
                        "payload": data.payload,
                    }
                )

                response.raise_for_status()
                forwarded = True

        except httpx.HTTPError:
            delivery_error = "Webhook forwarding failed"

    return {
        "message": "Webhook received successfully",
        "webhook": webhook,
        "delivery": {
            "forwarded": forwarded,
            "error": delivery_error
        }
    }


class Default(WorkerEntrypoint):

    async def fetch(self, request):
        app.state.hyperdrive = self.env.HYPERDRIVE

        app.state.webhook_destination = getattr(
            self.env,
            "WEBHOOK_DESTINATION_URL",
            None
        )

        return await asgi.fetch(app, request, self.env)

That's our complete FastAPI application.

We can now move on to testing it.

Step 9: Run the Application Locally

Before deploying anything, let's make sure our application works.

Run:

npm run dev

This command runs:

uv run pywrangler dev

PyWrangler starts the local Cloudflare Workers development environment.

You'll see a local URL in your terminal. Usually, it looks like this:

http://localhost:8787

Open it in your browser.

You should see:

{
  "message": "Welcome to the webhook relay api!",
  "platform": "Cloudflare workers"
}

Connect to Neon During Local Development

There's one thing you need to know about local development.

When you run the Worker locally, Hyperdrive doesn't automatically connect to your Neon database through the deployed binding.

Instead, you need to provide a direct PostgreSQL connection string.

Cloudflare supports this through an environment variable.

For our HYPERDRIVE binding, use:

CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE

On Linux or macOS, you can set it like this:

export CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE="postgresql://USERNAME:PASSWORD@HOSTNAME/DATABASE?sslmode=require"

Replace the example connection string with your actual Neon connection string.

Now start the development server:

npm run dev

Your local Worker can now connect directly to Neon.

One important difference: This local connection goes directly to Neon. It doesn't use Hyperdrive's connection pooling or query caching.

For deployed applications, Cloudflare uses the actual Hyperdrive binding.

When you're finished, you can remove the environment variable:

unset CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE

You can read more about this here: https://developers.cloudflare.com/hyperdrive/configuration/local-development/

Step 10: Test the API

Our local server is running. Let's test the endpoints.

You can use cURL, Postman, or any HTTP client you like.

Test the Welcome Endpoint

Run:

curl http://localhost:8787/

Expected response:

{
  "message": "Welcome to the webhook relay api!",
  "platform": "Cloudflare workers"
}

Test the Health Endpoint

curl http://localhost:8787/health

Expected response:

{
  "status": "Ok"
}

Create a Webhook Event

Now let's send a POST request.

curl -X POST \
  http://localhost:8787/webhooks \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "user.created",
    "payload": {
      "user_id": 123,
      "name": "John Doe",
      "email": "john@example.com"
    }
  }'

If everything works, you'll get a response containing the saved event.

For example:

{
  "message": "Webhook received successfully",
  "webhook": {
    "id": 1,
    "event_type": "user.created",
    "payload": {
      "user_id": 123,
      "name": "John Doe",
      "email": "john@example.com"
    },
    "created_at": "2026-11-03T10:00:00Z"
  },
  "delivery": {
    "forwarded": true,
    "error": null
  }
}

The ID and timestamp will be different in your database.

Get Recent Webhook Events

Now run:

curl http://localhost:8787/webhooks

You should see the event we just created.

If there are no events in the database, you'll get:

{
  "count": 0,
  "webhooks": []
}

Test Request Validation

Let's also check whether Pydantic validation works.

Send an empty event type:

curl -X POST \
  http://localhost:8787/webhooks \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "",
    "payload": {}
  }'

FastAPI should reject the request because event_type cannot be empty.

Great! Our API is working locally.

Step 11: Deploy FastAPI to Cloudflare Workers

Now it's time to deploy our application.

Before running the command, double-check these things:

  • You're logged in to Cloudflare.

  • Your Hyperdrive configuration exists.

  • Your Hyperdrive ID is correct.

  • The database table has been created.

  • Your Python dependencies are installed.

  • Your Wrangler configuration is correct.

Now run:

npm run deploy

This executes:

uv run pywrangler deploy

PyWrangler prepares the Python application and deploys it to Cloudflare Workers.

If everything goes well, you'll see a deployment success message in your terminal.

Wrangler will also show your Worker URL.

It will look something like this:

https://fastapi-cloudflare-workers.YOUR_SUBDOMAIN.workers.dev

That's it!

Your FastAPI application is now running on Cloudflare Workers.

You don't need to start Uvicorn or manage a VPS.

Step 12: Test the Deployed API

Let's make sure the deployed application works.

First, test the welcome endpoint:

curl https://YOUR_WORKER_URL/

Test the health endpoint:

curl https://YOUR_WORKER_URL/health

Now create a webhook:

curl -X POST \
  https://YOUR_WORKER_URL/webhooks \
  -H "Content-Type: application/json" \
  -d '{
    "event_type": "payment.completed",
    "payload": {
      "payment_id": "PAY-1001",
      "amount": 250,
      "currency": "USD"
    }
  }'

Finally, retrieve the stored events:

curl https://YOUR_WORKER_URL/webhooks

If everything works, your application is successfully connected to Neon PostgreSQL through Hyperdrive.

Try the Live Project

I've also deployed the completed project.

You can try it here:

Live API: https://tutorial-code.mdsamsuzzoha5222.workers.dev/

Here are the available endpoints:

Method

Endpoint

What it does

GET

/

Returns a welcome message

GET

/health

Checks API availability

POST

/webhooks

Receives and forwards events

GET

/webhooks

Gets recent webhook events

Try the health endpoint:

curl https://tutorial-code.mdsamsuzzoha5222.workers.dev/health

You can also send your own test webhook using the POST request from the previous section.

The database-backed endpoints depend on the live database configuration and availability.

Best Practices When Using FastAPI on Cloudflare Workers

Getting your application deployed is one thing. Keeping it reliable is another.

Here are a few things worth doing if you plan to use this setup for a real project.

1. Use Hyperdrive for PostgreSQL

If you're using PostgreSQL with Cloudflare Workers, Hyperdrive is worth considering.

It manages database connections and can cache eligible queries.

You can keep using your existing PostgreSQL database instead of moving all your data somewhere else.

2. Always Close Database Connections

Don't leave database connections open after finishing a query.

Use try and finally:

connection = await get_db_connection()

try:
    rows = await connection.fetch("SELECT * FROM webhook_events")
finally:
    await connection.close()

This makes sure the connection is closed even if the query fails.

3. Validate Incoming Data

Never assume that incoming requests contain valid data.

Use Pydantic models to check:

  • Required fields.

  • Data types.

  • String lengths.

  • JSON structures.

For more complex applications, you can create separate validation models for different event types.

4. Protect Your Webhook Endpoints

Our example doesn't include authentication.

That's fine for a learning project, but don't leave important webhook endpoints unprotected.

Consider adding:

  • API key authentication.

  • HMAC signature verification.

  • Timestamp checks.

  • Replay protection.

  • Rate limiting.

  • Request size limits.

These checks help prevent unwanted requests.

5. Be Careful With Webhook Destination URLs

Our application forwards events to a configured URL.

In a real application, don't let random users provide any URL they want.

That could create an SSRF vulnerability.

For example, someone might try to make your application send requests to internal services or private network addresses.

Use a list of trusted destinations and validate URLs before sending requests.

6. Handle Failed Webhook Deliveries

Right now, our application forwards the webhook immediately after saving it.

If the destination is down, the API returns a forwarding error.

But it doesn't try again.

For a more reliable system, you can use Cloudflare Queues.

The architecture would look like this:

Incoming Webhook
       |
       v
  FastAPI Worker
       |
       v
  Neon PostgreSQL
       |
       v
  Cloudflare Queue
       |
       v
  Consumer Worker
       |
       v
External Webhook

This way, receiving a webhook and delivering it become two separate jobs.

You can then add retries, delays, and failed-delivery tracking.

7. Keep Sensitive Information Private

Never commit database passwords, API keys, or private tokens to GitHub.

Use Cloudflare secrets instead.

For example:

npx wrangler secret put WEBHOOK_API_KEY

You can then access the secret through your Worker environment.

8. Monitor Your Application

It's also important to know when something goes wrong.

Keep an eye on:

  • Failed requests.

  • Database connection errors.

  • Webhook delivery failures.

  • Application errors.

  • Response times.

We've already enabled Cloudflare observability in our configuration:

"observability": {
  "enabled": true
}

This is a good starting point. For a real application, you should also add useful application logs and track webhook delivery results.

Common Mistakes When Deploying FastAPI on Cloudflare Workers

Here are some problems you might run into while following this tutorial.

1. Forgetting the Python Workers Flag

Make sure your wrangler.jsonc includes:

"compatibility_flags": [
  "python_workers"
]

Without it, the project won't use the Python Workers runtime.

2. Using an Old Compatibility Date

Hyperdrive support for Python Workers requires a compatibility date of September 8, 2026, or later.

Our project uses:

"compatibility_date": "2026-10-06"

If you use an older date, the required TCP socket support may not be available.

3. Using the Wrong Hyperdrive Binding Name

In wrangler.jsonc, we used:

"binding": "HYPERDRIVE"

So in Python, we must use:

self.env.HYPERDRIVE

The names must match.

4. Forgetting to Create the Database Table

Your database connection might work perfectly, but the API will still fail if the table doesn't exist.

Run this query in Neon:

SELECT * FROM webhook_events LIMIT 1;

If the table doesn't exist, run the SQL command from Step 3.

5. Not Configuring the Local Database Connection

If your API can't connect to Neon during local development, check this environment variable:

CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE

Make sure it contains the correct PostgreSQL connection string.

Also remember that local development connects directly to Neon, not through Hyperdrive.

6. Installing an Unsupported Python Package

This is one of the biggest differences between normal Python hosting and Python Workers.

Python Workers use Pyodide.

Some Python packages work perfectly, while others depend on native code or system features that aren't supported.

Before installing a package, check the Cloudflare Python Workers package documentation.

7. Assuming the Health Check Tests the Database

Our /health endpoint only checks whether the API responds.

It doesn't check PostgreSQL.

If you want to test database availability, create a separate endpoint that runs a simple query like:

SELECT 1;

8. Assuming Every Webhook Is Successfully Delivered

Saving an event and forwarding it are two different things.

Our API can save an event even when forwarding fails.

That's expected behavior in this project.

If you need reliable delivery, add a queue, retries, and delivery status tracking.

Advantages and Limitations

Before using this setup for a real application, it's worth looking at both the good and bad sides.

Advantages

1. No Traditional Server Management

You don't need to manage a VPS or keep a Uvicorn process running.

2. Runs on Cloudflare's Network

Your application runs on Cloudflare's global infrastructure.

3. You Can Use FastAPI

You can keep using familiar FastAPI features like routing, Pydantic validation, and asynchronous endpoints.

4. PostgreSQL Support

You can connect to an existing PostgreSQL database through Hyperdrive.

5. Simple Deployment

PyWrangler and Wrangler make it easier to develop and deploy Python Workers.

Limitations

1. Not Every Python Package Works

Python Workers use Pyodide, so some packages may not be compatible.

2. Different Runtime Environment

It's not the same as running Python directly on a Linux server.

Applications that depend on full operating system access may not work.

3. Database Setup Takes Extra Work

You need to configure Hyperdrive and handle local database connections separately.

4. Webhook Forwarding Is Synchronous

Our current implementation waits for the external endpoint to respond.

This can make requests slower.

5. Platform Limits Still Apply

Cloudflare has limits on execution time, resources, and other parts of the Workers runtime.

Check the official limits documentation before building a high-traffic application.

6. Python Hyperdrive Support Is in Beta

Cloudflare currently documents Python Workers Hyperdrive support as beta.

Check its latest status before using it for critical applications.

When Should You Use FastAPI on Cloudflare Workers?

So, when does this setup actually make sense?

It works well for smaller applications that don't need full control over a traditional server.

Good Use Cases

  • Webhook receivers.

  • Webhook relay APIs.

  • Small REST APIs.

  • Lightweight backend services.

  • API gateways.

  • Serverless integrations.

  • Applications that need PostgreSQL access.

When Should You Choose Something Else?

A traditional Python hosting platform might be a better choice if your application needs:

  • Long-running background tasks.

  • Heavy CPU processing.

  • Unsupported native Python packages.

  • Extensive filesystem access.

  • Special operating system features.

  • Full control over the Python environment.

Cloudflare Workers is not a replacement for every Python hosting platform.

Choose it when its runtime and limits fit your application.

Frequently Asked Questions

1. Can I deploy FastAPI on Cloudflare Workers?

Yes. Cloudflare supports FastAPI through its Python Workers ASGI integration.

You can use FastAPI routes, Pydantic validation, and asynchronous endpoints without running a traditional Uvicorn server.

2. Does FastAPI run directly on Cloudflare Workers?

FastAPI runs inside the Python Workers environment using Pyodide and Cloudflare's ASGI integration.

3. Do I need Uvicorn?

No. You don't need Uvicorn for this setup.

Cloudflare handles incoming requests and passes them to FastAPI through its ASGI integration.

4. Can Cloudflare Workers connect to PostgreSQL?

Yes. You can use Cloudflare Hyperdrive to connect Python Workers to supported PostgreSQL databases.

5. Can I use Neon PostgreSQL with Cloudflare Workers?

Yes. You can connect Neon PostgreSQL through Hyperdrive.

Create a Hyperdrive configuration using your Neon connection string, then add the binding to your Wrangler configuration.

6. Can I test FastAPI locally before deploying?

Yes. Run:

npm run dev

This starts the local Python Workers development server.

If you're using Hyperdrive, you'll also need to configure the local database connection string.

7. Is FastAPI on Cloudflare Workers good for production?

It can work for supported applications, but test everything first.

Pay attention to Python package compatibility, database connections, execution limits, and the current beta status of Python Hyperdrive support.

For important applications, don't skip testing and monitoring.

8. Is Cloudflare Workers better than a VPS?

It depends on what you're building.

Cloudflare Workers means less server management.

A VPS gives you more control over the operating system, Python environment, and background processes.

Choose the one that fits your project.

9. Which PostgreSQL drivers work with Python Workers?

Cloudflare documents asyncpg, pg8000, and psycopg as tested PostgreSQL drivers for Python Workers with Hyperdrive.

Check the latest documentation before choosing one.

10. Can I use other Cloudflare services with Python Workers?

Yes. Python Workers can use supported Cloudflare bindings and services.

The exact options depend on the service and the current Python Workers runtime support.

Conclusion

So, that's how you can deploy FastAPI on Cloudflare Workers. We started with a basic Python project and turned it into a working webhook relay API. Along the way, we:

  • Created a Python Workers project.

  • Installed dependencies using uv.

  • Configured Wrangler and PyWrangler.

  • Created a Neon PostgreSQL database.

  • Connected PostgreSQL through Cloudflare Hyperdrive.

  • Built API endpoints using FastAPI.

  • Stored webhook events in PostgreSQL.

  • Forwarded events using HTTPX.

  • Tested the application locally.

  • Deployed the API to Cloudflare Workers.

The main thing to remember is that Python Workers work a little differently from traditional Python hosting. Not every Python package will work, and database connections need some extra configuration. But once you understand how the runtime and ASGI integration work, building and deploying small Python APIs becomes much easier. If you're learning serverless development or looking for another way to host your FastAPI applications, give it a try. Start with a small project, test your dependencies, and see how well it fits your needs.

Additional Resources

Here are some useful links if you want to learn more:

Tags

# FastAPI# Cloudflare Workers# Python Workers# Cloudflare Hyperdrive# Neon PostgreSQL# FastAPI Deployment# Serverless Python# Webhook API
Keep Reading

Related Articles

Continue your learning journey