Django

Integrate the ISA SDK into a Django project: AppConfig singleton, middleware, and webhook verification.

Django

This guide wires the ISA SDK into a Django project using an AppConfig singleton so one client instance serves all requests. It covers the prequalify call, idempotency, error handling, and webhook verification.

Prerequisites

  • Python 3.10+, Django 4.2+
  • pip install isa-sdk django
  • ISA_TOKEN in your environment (via .env, secret manager, or deployment config)

Client singleton via AppConfig

Construct the Isa client once at startup. Django's AppConfig.ready() runs after all models are loaded — it's the right place.

# isa_integration/apps.py
from django.apps import AppConfig

class IsaIntegrationConfig(AppConfig):
    name = "isa_integration"
    isa = None  # populated in ready()

    def ready(self):
        import os
        from isa_sdk.zyins import Isa
        # Reads ISA_TOKEN from the environment.
        IsaIntegrationConfig.isa = Isa.with_bearer()
# isa_integration/__init__.py
default_app_config = "isa_integration.apps.IsaIntegrationConfig"

Add "isa_integration" to INSTALLED_APPS in settings.py.

Retrieve the singleton from any view:

from isa_integration.apps import IsaIntegrationConfig

isa = IsaIntegrationConfig.isa

Prequalify view

# isa_integration/views.py
from django.http import JsonResponse
from django.views import View

from isa_sdk.zyins import (
    Applicant,
    Coverage,
    ISAError,
    IsaApiError,
    NicotineDuration,
    NicotineUsageInput,
    ProductSelection,
    Sex,
    ValidationError,
)
from isa_sdk.catalog import Products

from isa_integration.apps import IsaIntegrationConfig


class PrequalifyView(View):
    def post(self, request):
        isa = IsaIntegrationConfig.isa

        try:
            # The SDK mints a UUID v4 idempotency key per call automatically.
            result = 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),
            )
        except ValidationError as err:
            return JsonResponse(
                {"error": str(err), "param": err.param}, status=400
            )
        except IsaApiError as err:
            return JsonResponse(
                {"error": str(err), "code": err.code, "request_id": err.request_id},
                status=502,
            )
        except ISAError as err:
            return JsonResponse({"error": str(err)}, status=500)

        plans = []
        for offer in result.data.plans:
            # Exactly one pricing row is the headline (primary=True).
            primary = next((row for row in offer.pricing if row.primary), None)
            premium = primary.premium if primary else None
            plans.append({
                "carrier":         offer.carrier.name,
                "product":         offer.product.name,
                # 'immediate' | 'graded' | 'rop'
                "category":        primary.eligibility.category if primary else None,
                # verbatim carrier string / integer cents
                "premium_display": premium.amount.display if premium else None,
                "premium_cents":   premium.amount.cents if premium else None,
            })
        return JsonResponse({"plans": plans, "request_id": result.request_id})
# isa_integration/urls.py
from django.urls import path
from .views import PrequalifyView

urlpatterns = [
    path("prequalify/", PrequalifyView.as_view()),
]

Webhook verification

Django's CSRF middleware exempts views decorated with @csrf_exempt. The ISA SDK does not ship a Django-specific helper, but the verification is three lines.

# isa_integration/webhook_views.py
import hashlib
import hmac
import json
import os
import time

from django.http import HttpResponse, HttpResponseBadRequest
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator


WEBHOOK_SECRET = os.environ["ISA_WEBHOOK_SECRET"]
TIMESTAMP_TOLERANCE_SECONDS = 300


def _verify_signature(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) > TIMESTAMP_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)


@method_decorator(csrf_exempt, name="dispatch")
class WebhookView(View):
    def post(self, request):
        sig_header = request.headers.get("X-ZyINS-Signature", "")
        if not _verify_signature(request.body, sig_header):
            return HttpResponseBadRequest("invalid signature")

        event = json.loads(request.body)
        event_type = event.get("type")

        if event_type == "license.activated":
            _handle_license_activated(event)

        return HttpResponse(status=200)


def _handle_license_activated(event):
    pass  # persist event["data"]["license_id"], update your records

Settings checklist

# settings.py (additions only)

# ISA_TOKEN loaded from environment; never hardcode here.
# Use python-decouple, django-environ, or your secrets manager.

INSTALLED_APPS = [
    # ...
    "isa_integration",
]

# The SDK honors ISA_BASE_URL for staging.
# ISA_BASE_URL = "https://staging.zyins.isaapi.com"

See also