import datetime as dt
from sqlalchemy import Column, DateTime, Integer, String, UniqueConstraint
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class WebhookDelivery(Base):
__tablename__ = "webhook_deliveries"
__table_args__ = (
UniqueConstraint("dedupe_key", name="uq_webhook_dedupe_key"),
)
id = Column(Integer, primary_key=True)
dedupe_key = Column(String(255), nullable=False)
provider = Column(String(64), nullable=False)
event_id = Column(String(255), nullable=False)
status = Column(String(32), nullable=False, default="processing")
received_at = Column(DateTime, default=dt.datetime.utcnow, nullable=False)
completed_at = Column(DateTime, nullable=True)
import datetime as dt
from dataclasses import dataclass
from sqlalchemy import select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.orm import Session
from .models import WebhookDelivery
@dataclass
class ClaimResult:
delivery: WebhookDelivery
is_new: bool
def claim_delivery(db: Session, dedupe_key: str, provider: str, event_id: str) -> ClaimResult:
stmt = (
pg_insert(WebhookDelivery)
.values(dedupe_key=dedupe_key, provider=provider, event_id=event_id, status="processing")
.on_conflict_do_nothing(index_elements=["dedupe_key"])
.returning(WebhookDelivery.id)
)
inserted_id = db.execute(stmt).scalar_one_or_none()
db.commit()
if inserted_id is not None:
row = db.get(WebhookDelivery, inserted_id)
return ClaimResult(delivery=row, is_new=True)
existing = db.execute(
select(WebhookDelivery).where(WebhookDelivery.dedupe_key == dedupe_key)
).scalar_one()
return ClaimResult(delivery=existing, is_new=False)
def mark_succeeded(db: Session, delivery: WebhookDelivery) -> None:
delivery.status = "succeeded"
delivery.completed_at = dt.datetime.utcnow()
db.commit()
import hashlib
import hmac
from fastapi import APIRouter, BackgroundTasks, Depends, Header, HTTPException, Request
from sqlalchemy.orm import Session
from .db import get_db
from .idempotency import claim_delivery, mark_succeeded
from .processing import handle_event
router = APIRouter()
WEBHOOK_SECRET = b"whsec_replace_me"
def verify_signature(body: bytes, signature: str) -> None:
expected = hmac.new(WEBHOOK_SECRET, body, hashlib.sha256).hexdigest()
if not signature or not hmac.compare_digest(expected, signature):
raise HTTPException(status_code=401, detail="invalid signature")
@router.post("/webhooks/stripe")
async def receive_stripe(
request: Request,
background: BackgroundTasks,
stripe_signature: str = Header(default="", alias="Stripe-Signature"),
db: Session = Depends(get_db),
):
body = await request.body()
verify_signature(body, stripe_signature)
payload = await request.json()
event_id = payload.get("id")
if not event_id:
raise HTTPException(status_code=400, detail="missing event id")
dedupe_key = f"stripe:{event_id}"
result = claim_delivery(db, dedupe_key, provider="stripe", event_id=event_id)
if not result.is_new:
if result.delivery.status == "succeeded":
return {"status": "duplicate", "event_id": event_id}
return {"status": "in_progress", "event_id": event_id}
def _process():
handle_event(payload)
mark_succeeded(db, result.delivery)
background.add_task(_process)
return {"status": "accepted", "event_id": event_id}
Webhook senders like Stripe or GitHub guarantee at-least-once delivery, which means the same event can arrive twice — after a network blip, a timeout, or a manual retry. Processing a duplicate can double-charge a customer or send two emails, so the receiver has to make handling idempotent. This snippet shows the classic pattern: a durable dedupe store keyed on the provider's event id, checked and inserted atomically before any side effects run.
In models.py, WebhookDelivery is the store — one row per unique delivery, with a dedupe_key under a UniqueConstraint. The uniqueness is enforced by the database, not application logic, which is what makes the check race-safe: two concurrent requests carrying the same key cannot both win the insert. The status column lets the system distinguish a delivery that is still processing from one that succeeded, so a retry that arrives while the first is mid-flight can be told to back off rather than re-run.
idempotency.py holds the core claim_delivery helper. It attempts an INSERT and relies on Postgres' ON CONFLICT (dedupe_key) DO NOTHING to detect an existing row without raising. If INSERT ... RETURNING yields no id, the key was already claimed, so the function returns the existing delivery instead. This turns a potential exception-driven flow into a single round trip. Marking the row succeeded via mark_succeeded happens only after the business logic commits.
webhooks.py wires it into a FastAPI route. verify_signature runs first — an unverified body must never touch the store, since an attacker could otherwise poison dedupe keys. The dedupe_key combines provider and event id so keys never collide across sources. When claim_delivery reports the event was already succeeded, the endpoint returns 200 immediately, which is exactly what the sender wants: a fast, successful ack that stops further retries. New events are handed to a background task so the HTTP response stays quick.
The main trade-off is storage growth — old rows should be pruned on a TTL — and the store must live in a shared, transactional database, not in-process memory, for the guarantee to hold across replicas.
Related snips
timestamp = request.headers.fetch('X-Signature-Timestamp')
signature = request.headers.fetch('X-Signature')
payload = request.raw_post
data = "#{timestamp}.#{payload}"
expected = OpenSSL::HMAC.hexdigest('SHA256', ENV.fetch('WEBHOOK_SECRET'), data)
HMAC signed API requests for webhook and partner integrity
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
Share this code
Here's the card — post it anywhere.