FastAPI
Integrate the ISA SDK into a FastAPI application: lifespan singleton, dependency injection, and async webhook verification.
FastAPI
This guide wires the ISA SDK into a FastAPI application using the lifespan pattern for a singleton client, dependency injection for per-route access, and async-safe webhook verification.
Prerequisites
- Python 3.10+, FastAPI 0.110+
pip install isa-sdk fastapi uvicornISA_TOKENin your environment
Singleton via lifespan
The Isa client is thread-safe and intended to be constructed once. FastAPI's lifespan context manager is the correct place.
# main.py
import os
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from fastapi import FastAPI
from isa_sdk.zyins import Isa
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
# Reads ISA_TOKEN from the environment.
app.state.isa = Isa.with_bearer()
yield
# SDK has no async teardown — nothing to do here.
app = FastAPI(lifespan=lifespan)Dependency injection
# dependencies.py
from fastapi import Request
from isa_sdk.zyins import Isa
def get_isa(request: Request) -> Isa:
return request.app.state.isaPrequalify endpoint
# routes/prequalify.py
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from isa_sdk.zyins import (
Applicant,
Coverage,
Isa,
IsaApiError,
NicotineDuration,
NicotineUsageInput,
ProductSelection,
Sex,
ValidationError,
)
from isa_sdk.catalog import Products
from dependencies import get_isa
router = APIRouter()
class PrequalifyRequest(BaseModel):
dob: str
state: str
height_inches: int
weight_pounds: int
class PlanOut(BaseModel):
carrier: str
product: str
category: str | None # 'immediate' | 'graded' | 'rop'
premium_display: str | None # verbatim carrier string
premium_cents: int | None # integer cents
class PrequalifyResponse(BaseModel):
plans: list[PlanOut]
request_id: str
@router.post("/prequalify", response_model=PrequalifyResponse)
def run_prequalify(
body: PrequalifyRequest,
isa: Annotated[Isa, Depends(get_isa)],
) -> PrequalifyResponse:
try:
# The SDK mints a UUID v4 idempotency key per call automatically.
result = isa.zyins.prequalify(
applicant=Applicant(
dob=body.dob,
sex=Sex.MALE,
height_inches=body.height_inches,
weight_pounds=body.weight_pounds,
state=body.state,
nicotine_use=NicotineUsageInput(last_used=NicotineDuration.NEVER),
),
coverage=Coverage.face_value(25_000),
products=ProductSelection.of(Products.Fex.AetnaAccendo),
)
except ValidationError as err:
raise HTTPException(status_code=400, detail={"param": err.param, "message": str(err)})
except IsaApiError as err:
raise HTTPException(status_code=502, detail={"code": err.code, "request_id": err.request_id})
plans: list[PlanOut] = []
for offer in result.data.plans:
primary = next((row for row in offer.pricing if row.primary), None)
premium = primary.premium if primary else None
plans.append(PlanOut(
carrier=offer.carrier.name,
product=offer.product.name,
category=primary.eligibility.category if primary else None,
premium_display=premium.amount.display if premium else None,
premium_cents=premium.amount.cents if premium else None,
))
return PrequalifyResponse(plans=plans, request_id=result.request_id)Register the router in main.py:
from routes.prequalify import router as prequalify_router
app.include_router(prequalify_router)Async note
The isa-sdk client is synchronous. For high-concurrency FastAPI applications, run SDK calls in a thread pool to avoid blocking the event loop:
import asyncio
from isa_sdk.zyins import Applicant, Coverage, Isa, NicotineDuration, NicotineUsageInput, ProductSelection, Sex
from isa_sdk.catalog import Products
async def run_quote(isa: Isa):
return await asyncio.to_thread(
lambda: isa.zyins.prequalify(
applicant=Applicant(
dob="1962-04-18",
sex=Sex.MALE,
height_inches=70,
weight_pounds=195,
state="NC",
nicotine_use=NicotineUsageInput(last_used=NicotineDuration.NEVER),
),
coverage=Coverage.face_value(25_000),
products=ProductSelection.of(Products.Fex.AetnaAccendo),
)
)Webhook verification
# routes/webhooks.py
import hashlib
import hmac
import json
import os
import time
from fastapi import APIRouter, HTTPException, Request, Response
router = APIRouter()
WEBHOOK_SECRET = os.environ["ISA_WEBHOOK_SECRET"]
TOLERANCE_SECONDS = 300
def _verify(raw_body: bytes, sig_header: str) -> bool:
# X-ZyINS-Signature is "t=<unix-seconds>,v1=<hex>".
fields = dict(part.split("=", 1) for part in sig_header.split(",") if "=" in part)
try:
ts = int(fields["t"])
sig = fields["v1"]
except (KeyError, ValueError):
return False
if abs(time.time() - ts) > TOLERANCE_SECONDS:
return False
expected = hmac.new(
WEBHOOK_SECRET.encode(),
f"{ts}.".encode() + raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, sig)
@router.post("/webhooks/isa")
async def receive_webhook(request: Request) -> Response:
raw = await request.body()
sig = request.headers.get("X-ZyINS-Signature", "")
if not _verify(raw, sig):
raise HTTPException(status_code=400, detail="invalid signature")
event = json.loads(raw)
# Dispatch on event["type"]
return Response(status_code=200)See also
Updated about 9 hours ago