Webhooks & Automation October 2026

Trigger a Python Function via Webhook — No Server Required

Run Python code in the cloud the moment an external event fires — a Stripe payment, a GitHub push, a form submission. Here's how to set up a webhook-triggered Python function without managing a server.

L
LiteLambda Team
7 min read

A webhook is an HTTP request that an external service sends to your URL when something happens: a Stripe payment succeeds, a GitHub PR is merged, a form is submitted, a Slack command is invoked.

The traditional way to handle a webhook is to run a web server — a Flask or FastAPI app — that stays up 24/7 listening for these requests. For a script that runs 10 times a day and takes 200ms each time, you're paying for 23.9 hours of idle server time to handle 2 seconds of actual work.

This guide shows how to trigger Python functions via webhook without running a persistent server.


The Problem with Traditional Webhook Handlers

A typical Flask webhook handler:

# Traditional approach — requires a running server
from flask import Flask, request, jsonify
import stripe

app = Flask(__name__)

@app.route("/webhook/stripe", methods=["POST"])
def stripe_webhook():
    payload = request.get_data()
    sig_header = request.headers.get("Stripe-Signature")

    try:
        event = stripe.Webhook.construct_event(
            payload, sig_header, "whsec_your_secret"
        )
    except ValueError:
        return jsonify({"error": "Invalid payload"}), 400
    except stripe.error.SignatureVerificationError:
        return jsonify({"error": "Invalid signature"}), 400

    if event["type"] == "payment_intent.succeeded":
        payment_intent = event["data"]["object"]
        send_receipt_email(payment_intent)
        update_crm(payment_intent)
        trigger_fulfillment(payment_intent)

    return jsonify({"received": True})

if __name__ == "__main__":
    app.run(port=8000)

To run this in production you need:
- A server (EC2, Hetzner, Render) — running 24/7
- A public IP and domain
- SSL certificate
- A process manager (systemd, gunicorn) to keep it alive
- Monitoring so you know when it crashes

For code that runs for a few milliseconds per event, this is significant overhead.


Option 1: Webhook via Cloud Functions (AWS Lambda / GCP Cloud Functions)

Cloud FaaS platforms solve the "always-on server" problem. Your code only runs when a request comes in:

# AWS Lambda handler
import json
import stripe
import os

def lambda_handler(event, context):
    body = event.get("body", "")
    headers = event.get("headers", {})
    sig_header = headers.get("Stripe-Signature", "")

    try:
        stripe_event = stripe.Webhook.construct_event(
            body, sig_header, os.environ["STRIPE_WEBHOOK_SECRET"]
        )
    except (ValueError, stripe.error.SignatureVerificationError) as e:
        return {"statusCode": 400, "body": json.dumps({"error": str(e)})}

    if stripe_event["type"] == "payment_intent.succeeded":
        pi = stripe_event["data"]["object"]
        process_successful_payment(pi["id"], pi["amount"])

    return {"statusCode": 200, "body": json.dumps({"received": True})}

The friction: AWS Lambda requires an IAM role, an API Gateway to give it a public URL, a deployment package or container, and CloudWatch for logs. Setup takes 2–4 hours for someone unfamiliar with AWS. Per-invocation billing and cold starts are additional considerations.


Option 2: Webhook-Triggered Python Function on LiteLambda

LiteLambda gives every Python function a public HTTPS URL that can be triggered by any webhook without any infrastructure setup.

Step 1: Write your webhook handler

# stripe_webhook.py
import stripe
import os
import requests

def handler(event, context):
    """
    Handles incoming Stripe webhook events.

    event: dict containing 'body' (raw payload), 'headers', 'method'
    context: execution context with env vars
    """
    body = event.get("body", "")
    headers = event.get("headers", {})
    sig_header = headers.get("stripe-signature", "")

    # Verify the webhook signature
    try:
        stripe_event = stripe.Webhook.construct_event(
            body,
            sig_header,
            os.environ["STRIPE_WEBHOOK_SECRET"]
        )
    except ValueError as e:
        return {"status": 400, "error": "Invalid payload"}
    except stripe.error.SignatureVerificationError as e:
        return {"status": 400, "error": "Invalid signature"}

    event_type = stripe_event["type"]

    if event_type == "payment_intent.succeeded":
        payment_intent = stripe_event["data"]["object"]

        # 1. Send receipt email
        send_receipt(
            email=payment_intent["receipt_email"],
            amount=payment_intent["amount"],
            currency=payment_intent["currency"],
            payment_id=payment_intent["id"]
        )

        # 2. Notify Slack
        requests.post(
            os.environ["SLACK_WEBHOOK_URL"],
            json={
                "text": f"💰 Payment succeeded: {payment_intent['amount']/100:.2f} {payment_intent['currency'].upper()}"
            },
            timeout=5
        )

        print(f"✓ Processed payment: {payment_intent['id']}")

    elif event_type == "payment_intent.payment_failed":
        payment_intent = stripe_event["data"]["object"]
        handle_failed_payment(payment_intent)

    return {"status": 200, "received": True}


def send_receipt(email, amount, currency, payment_id):
    # Your email sending logic (SendGrid, Mailgun, etc.)
    pass

def handle_failed_payment(payment_intent):
    # Your failed payment logic
    pass

Step 2: Deploy as a webhook function

pip install litelambda-cli
litelambda login

litelambda deploy stripe-webhook stripe_webhook.py \
  --type webhook \
  --env STRIPE_WEBHOOK_SECRET=whsec_xxxx \
  --env SLACK_WEBHOOK_URL=https://hooks.slack.com/...

You get back a public HTTPS URL:

https://run.litelambda.in/w/stripe-webhook/YOUR_FUNCTION_KEY

Step 3: Register the URL in Stripe

Go to Stripe Dashboard → Webhooks → Add endpoint. Paste the LiteLambda URL. Select events (payment_intent.succeeded, payment_intent.payment_failed). Done.

Now every Stripe payment event runs your Python function. No server. No idle costs. Full execution logs available in the LiteLambda dashboard.


Sync vs Async Webhook Functions

Synchronous (default)

The external service waits for your function to return. Use when:
- The caller expects a response payload (like a Slack slash command)
- You need to return a status code that affects the caller's behaviour

def handler(event, context):
    # Process the webhook
    result = process_event(event)

    # Return within the timeout (Stripe expects a response within 30 seconds)
    return {"status": 200, "processed": True, "id": result["id"]}

Asynchronous (fire-and-forget)

LiteLambda immediately returns 202 Accepted to the caller and runs your function in the background. Use when:
- Processing takes more than a few seconds
- The caller doesn't need a result (GitHub, most event-driven systems)

litelambda deploy github-webhook github_handler.py \
  --type webhook \
  --async \
  --env GITHUB_TOKEN=ghp_xxxx

The calling service gets an immediate 202 response, and your function runs to completion without blocking it.


Real-World Webhook Use Cases

GitHub: Run code review on every PR

# github_pr_handler.py
import os
import requests

GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")

def handler(event, context):
    payload = event.get("body_json", {})  # Parsed JSON body

    if payload.get("action") != "opened":
        return {"status": 200, "skipped": True}

    pr = payload["pull_request"]
    repo = payload["repository"]["full_name"]
    pr_number = pr["number"]

    # Get the diff
    diff_response = requests.get(
        f"https://api.github.com/repos/{repo}/pulls/{pr_number}/files",
        headers={"Authorization": f"Bearer {GITHUB_TOKEN}"},
        timeout=15
    )
    files = diff_response.json()

    # Post a summary comment
    large_files = [f for f in files if f.get("changes", 0) > 300]
    if large_files:
        comment = f"⚠️ This PR has {len(large_files)} large files (300+ lines changed). Consider splitting."
        requests.post(
            f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments",
            headers={"Authorization": f"Bearer {GITHUB_TOKEN}"},
            json={"body": comment},
            timeout=10
        )

    return {"status": 200, "files_checked": len(files), "flagged": len(large_files)}

Slack Slash Command: Return real data

Slack slash commands require a response within 3 seconds. With a sync webhook function:

# slack_command.py
import os
import requests

def handler(event, context):
    body = event.get("body", "")
    params = dict(kv.split("=") for kv in body.split("&"))

    command = params.get("command", "")
    text = params.get("text", "").strip()

    if command == "/deploy-status":
        # Check your deployment system
        status = get_deployment_status(text or "production")
        return {
            "status": 200,
            "body": {
                "response_type": "in_channel",
                "text": f"Deploy status for *{text or 'production'}*: {status}"
            }
        }

    return {"status": 200, "body": {"text": f"Unknown command: {command}"}}

Form Submission: Process and store

# form_handler.py
import os
import json
import requests
from datetime import datetime

def handler(event, context):
    data = event.get("body_json", {})

    name = data.get("name", "").strip()
    email = data.get("email", "").strip()
    message = data.get("message", "").strip()

    if not all([name, email, message]):
        return {"status": 400, "error": "Missing required fields"}

    # Store in your database or Notion or Airtable
    requests.post(
        os.environ["AIRTABLE_URL"],
        headers={"Authorization": f"Bearer {os.environ['AIRTABLE_TOKEN']}"},
        json={
            "fields": {
                "Name": name,
                "Email": email,
                "Message": message,
                "Submitted": datetime.utcnow().isoformat()
            }
        },
        timeout=10
    )

    # Send Slack notification
    requests.post(
        os.environ["SLACK_WEBHOOK_URL"],
        json={"text": f"📬 New contact: {name} ({email})"},
        timeout=5
    )

    return {"status": 200, "received": True}

Combining Webhooks with Cron Jobs

The real power comes when you combine both. Example: a monitoring pipeline:

GitHub Push  →  Webhook Function  →  Run tests, store results in KV store
                                                       ↓
Daily 8am  →  Cron Job  →  Read results from KV store  →  Email weekly report

Both functions share the same KV store via context.kv:

# webhook_handler.py (runs on every push)
def handler(event, context):
    run_id = run_tests()
    context.kv.set(f"test_result:{run_id}", {"status": "pass", "date": today()})
    return {"status": 200}

# report_generator.py (runs daily at 8am via cron)
def handler(event, context):
    results = context.kv.get_all("test_result:")
    generate_and_email_report(results)

Webhook Security: Always Verify Signatures

Never trust an incoming webhook without verifying it came from the expected source. Every major platform provides a signature mechanism:

import hmac
import hashlib
import os

def verify_github_signature(payload_body: str, signature_header: str) -> bool:
    """Verify GitHub webhook signature."""
    if not signature_header:
        return False

    secret = os.environ.get("GITHUB_WEBHOOK_SECRET", "").encode()
    expected = "sha256=" + hmac.new(
        secret,
        payload_body.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(expected, signature_header)


def handler(event, context):
    body = event.get("body", "")
    sig = event.get("headers", {}).get("x-hub-signature-256", "")

    if not verify_github_signature(body, sig):
        return {"status": 401, "error": "Invalid signature"}

    # Process the verified webhook
    payload = event.get("body_json", {})
    # ...

Summary

Approach Server required? Setup time Idle cost
Flask/FastAPI on VPS ✅ Yes 1–2 hours ~$5–10/mo always-on
AWS Lambda + API Gateway ❌ No 2–4 hours Per-invocation
LiteLambda Webhook Function ❌ No ~10 minutes Per-invocation

If you have an existing script that needs to be triggered by an external service — Stripe, GitHub, Slack, a form, a CI pipeline — deploying it as a webhook function on LiteLambda eliminates the server overhead and gets you a working HTTPS endpoint in minutes.

Deploy your first webhook function →


Related: How to Get Email Alerts When Your Python Cron Job Fails · Python Cron Jobs vs Webhooks: When to Use Each · LiteLambda Webhook Documentation

Skip the infrastructure setup.

Run this exact code in our secure, isolated Docker sandbox. It takes 10 seconds to deploy.

Deploy this script in 60s →

No DevOps required.