Flask
Integrate the ISA SDK into a Flask application: application factory, client helper, and webhook blueprint.
Flask
This guide wires the ISA SDK into a Flask application using the application factory pattern. One Isa client is created per process, stored on the Flask app object, and retrieved through a small helper. The client is thread-safe, so a single instance serves every request a worker handles.
Prerequisites
- Python 3.10+, Flask 3.0+
pip install isa-sdk flaskISA_TOKENin your environment — your API token (isa_test_…while you build,isa_live_…in production). Tokens arrive by email after checkout; contact support if you need one reissued.
The SDK authenticates with this bearer token alone. There is nothing else to configure.
Application factory
# app/__init__.py
from flask import Flask
from isa_sdk import Isa
def create_app() -> Flask:
app = Flask(__name__)
# Reads ISA_TOKEN from the environment and authenticates with it.
app.isa = Isa.with_bearer()
from .routes import bp
app.register_blueprint(bp)
from .webhooks import wh
app.register_blueprint(wh, url_prefix="/webhooks")
return app
Helper to retrieve the client
# app/isa_client.py
from flask import current_app
from isa_sdk import Isa
def get_isa() -> Isa:
return current_app.isa # type: ignore[attr-defined]
Prequalify route
A prequalification request has three parts:
applicant— demographics (age, sex, height, weight, state).coverage— face amount in cents plus the state.products— which carrier products to evaluate (empty = all qualifying products).
An empty products selection quotes every product the applicant qualifies for, which is the right default for a first integration. Narrow to specific product IDs once you have a shortlist.
Each offer includes a pricing[] table with one row per rate class. The row flagged primary is the best-qualifying class to surface.
# app/routes.py
import uuid
from flask import Blueprint, jsonify, request
from isa_sdk import Coverage, Sex
from isa_sdk.zyins import (
Applicant,
IsaApiError,
NicotineUsage,
PrequalifyInput,
ValidationError,
)
from .isa_client import get_isa
bp = Blueprint("isa", __name__)
ALL_PRODUCTS = "" # empty selection means "quote every product the applicant qualifies for"
@bp.post("/prequalify")
def prequalify():
isa = get_isa()
body = request.get_json(force=True)
input_data = PrequalifyInput(
applicant=Applicant(
dob=body["dob"],
sex=Sex.MALE,
height_inches=body["height_inches"],
weight_pounds=body["weight_pounds"],
state=body["state"],
nicotine_use=NicotineUsage.NONE,
),
coverage=Coverage.face_value(25_000),
products=ALL_PRODUCTS,
)
try:
result = isa.zyins.prequalify(
input_data,
idempotency_key=str(uuid.uuid4()),
)
except ValidationError as err:
return jsonify({"error": str(err), "param": err.param}), 400
except IsaApiError as err:
return jsonify({"error": str(err), "code": err.code}), 502
plans = []
for offer in result.data.plans:
primary = next(row for row in offer.pricing if row.primary)
plans.append({
"carrier": offer.carrier.name,
"product": offer.product.name,
"category": primary.eligibility.category,
"premium_display": primary.premium.amount.display,
"premium_cents": primary.premium.amount.cents,
})
return jsonify({"plans": plans, "request_id": result.request_id})
A request to this route carries only the demographics:
curl -s http://localhost:5000/prequalify \
-H "Content-Type: application/json" \
-d '{"dob":"1962-04-18","height_inches":70,"weight_pounds":195,"state":"NC"}'
The raw POST /v3/prequalify body and response shape are documented in the Run a prequalification reference.
Webhook blueprint
ZyINS signs each webhook with a single X-ZyINS-Signature: t=<unix-seconds>,v1=<hex> header. Parse both fields, recompute the HMAC over <t>.<raw_body>, verify in constant time, and reject any request more than five minutes old. The event id lives in the JSON body (id), not a header — deduplicate on it since delivery is at-least-once. The signing secret comes from the ISA_WEBHOOK_SECRET environment variable.
# app/webhooks.py
import hashlib
import hmac
import json
import os
import time
from flask import Blueprint, Response, request
wh = Blueprint("webhooks", __name__)
WEBHOOK_SECRET = os.environ["ISA_WEBHOOK_SECRET"]
TOLERANCE_SECONDS = 300
_seen_event_ids: set[str] = set() # swap for a durable store in production
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
payload = f"{ts}.".encode() + raw_body
expected = hmac.new(
WEBHOOK_SECRET.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig)
@wh.post("/isa")
def receive():
raw = request.get_data()
sig = request.headers.get("X-ZyINS-Signature", "")
if not _verify(raw, sig):
return Response("invalid signature", status=400)
event = json.loads(raw)
event_id = event.get("id", "")
if event_id in _seen_event_ids:
return Response(status=200) # already processed; acknowledge and skip
_seen_event_ids.add(event_id)
_dispatch(event)
return Response(status=200)
def _dispatch(event: dict) -> None:
pass # switch on event["type"]
The shared Webhooks guide documents the full signing protocol, header set, and retry schedule.
Production notes
- Run with a production WSGI server (
gunicorn) and multiple workers. TheIsa
client is process-local and thread-safe — each worker has its own instance. - Pass
base_url=toIsa.with_bearer(base_url="https://zyins.staging.isaapi.com")
to target a staging environment. - Replace the in-memory
_seen_event_idsset with a shared store (Redis, your
database) so deduplication holds across workers and restarts.
See also
Updated about 8 hours ago