Skip to content
SignYu
TemplatesPricingAPIDocsAboutBlogContact
  1. Home
  2. API
  3. Integrations
  4. Python

Aadhaar eSign API in Python with FastAPI Webhooks

The official signyu package on PyPI wraps the REST API for scripts, workers and web apps. This guide sends a document from a plain Python script, then receives webhooks in FastAPI. If you use Django, see the Django guide, which covers its CSRF and request.body details.

Updated 2026-10-01

What you need

  • Python 3.9 or newer.
  • A SignYu API key (starts with sk_live_). API access costs ₹999/month with a 3-day free trial; create the key under Developers in the dashboard.
  • Signature credits for your signers, from ₹15 per signature (₹15 per signature on packs of 10 or more). Sending uses one credit per signer.
  • A webhook endpoint added under Developers, and its signing secret.

Step 1: Install

Install the SDK, plus FastAPI and Uvicorn for the webhook receiver.

pip install signyu fastapi uvicorn

export SIGNYU_API_KEY=sk_live_your_api_key
export SIGNYU_WEBHOOK_SECRET=your_endpoint_secret

Step 2: Create, add signers and send

documents.create accepts a path, bytes or a binary file object. Responses are plain dicts with the API's camelCase keys.

import os
from signyu import SignYu

client = SignYu(api_key=os.environ["SIGNYU_API_KEY"])

doc = client.documents.create(
    file="contracts/nda-rohan-mehta.pdf",
    name="NDA, Rohan Mehta",
)
document_id = doc["documentId"]

client.documents.add_signers(document_id, [
    {"name": "Rohan Mehta", "phone": "9876543210", "email": "rohan@example.com"},
    {"name": "Neha Iyer", "phone": "9812345678", "email": "neha@yourcompany.in"},
])

sent = client.documents.send(document_id)
print("credits left:", sent["creditsRemaining"])
for signer in sent["signers"]:
    print(signer["signingOrder"], signer["name"], signer["signUrl"])

Step 3: Handle errors

SignYuError carries status and code. A 402 means nothing was sent and no credits were taken; a 409 means the document was already sent.

from signyu import SignYuError

try:
    client.documents.send(document_id)
except SignYuError as e:
    if e.code == "insufficient_credits":      # 402
        alert_finance(e.message)
    elif e.code == "invalid_state":           # 409, already sent
        pass
    elif e.status in (401, 403):              # bad key or API access off
        raise RuntimeError("Check SIGNYU_API_KEY and the API subscription") from e
    else:
        raise

Step 4: Download the signed PDF and certificate

When status is COMPLETED, download from downloadUrl with a plain HTTP GET, and save the completion certificate with its audit trail next to it.

import urllib.request
from pathlib import Path

doc = client.documents.get(document_id)
if doc["status"] == "COMPLETED":
    out = Path("signed")
    out.mkdir(exist_ok=True)
    with urllib.request.urlopen(doc["downloadUrl"]) as resp:  # no auth header
        (out / f"{document_id}.pdf").write_bytes(resp.read())
    cert = client.documents.get_certificate(document_id)
    (out / f"{document_id}-certificate.pdf").write_bytes(cert)

Verify webhooks

In FastAPI, await request.body() returns the exact bytes SignYu signed. Do not declare a Pydantic model as the handler parameter, because FastAPI would parse the body before you can verify it. The SDK client is synchronous, so push follow-up API calls to a background task instead of calling them inside the async handler.

import hashlib
import hmac
import json
import os

from fastapi import BackgroundTasks, FastAPI, HTTPException, Request
from signyu import SignYu

app = FastAPI()
client = SignYu(api_key=os.environ["SIGNYU_API_KEY"])
SECRET = os.environ["SIGNYU_WEBHOOK_SECRET"].encode()


def archive(document_id: str) -> None:
    doc = client.documents.get(document_id)
    save_signed_pdf(document_id, doc["downloadUrl"])


@app.post("/webhooks/signyu")
async def signyu_webhook(request: Request, background: BackgroundTasks):
    raw = await request.body()
    header = request.headers.get("x-signsetu-signature", "")
    expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(header, expected):
        raise HTTPException(status_code=400, detail="invalid signature")

    event = json.loads(raw)
    if event["event"] == "document.completed":
        background.add_task(archive, event["documentId"])
    return {"ok": True}

Common mistakes

  • Declaring the webhook body as a Pydantic model means you verify a re-serialized dict, which will not match. Read request.body() instead.
  • Calling the synchronous SDK inside an async def handler blocks the event loop; use BackgroundTasks, a thread pool or a worker queue.
  • Phone numbers stored as integers lose formatting checks. Send them as strings of digits, at least 10 long.
  • Send is one way. A second POST to /send returns 409 invalid_state, and signers cannot be added after sending. Add every signer (up to 6) before you call send.
  • The downloadUrl is a temporary presigned storage link. Download it with a plain GET and no Authorization header (sending your Bearer key to it makes the request fail), and do not store the URL itself; call GET /api/v1/documents/{id} again for a fresh one.

Frequently asked questions

Is the Python SDK async?

The client is synchronous. In async frameworks run it in a thread pool or a background task, or call the REST API directly with httpx.AsyncClient.

Can I pass a file uploaded to my web app straight to documents.create?

Yes. Pass the bytes or a binary file object, and set name so the document is labelled sensibly in your dashboard.

Which Python web frameworks does this work with?

Any of them. The verification only needs the raw request bytes and the X-SignSetu-Signature header. In Flask use request.get_data(); in Django use request.body.

How do I test webhooks locally?

Expose your local server with a tunnel such as ngrok or cloudflared, add that HTTPS URL as an endpoint under Developers, and send a real low-cost test document.

API reference

  • Quickstart
  • Documents
  • Webhooks
  • Errors

Other integration guides

  • Aadhaar eSign API in Node.js with Express
  • Aadhaar eSign in Django: Models, Views and Webhooks
  • Send Aadhaar eSign Requests from Google Sheets

Get your API key

Start a 3-day free trial of API access and send your first document today.

Start free trialSee API pricing and features
SignYu

Pay-per-use Aadhaar eSign for Indian businesses, landlords, and individuals. Sign PDFs in 2 minutes at ₹15 per signature.

LinkedIn →

Product

  • Aadhaar eSign
  • Pricing
  • Templates
  • Rent Agreement eSign
  • Verify Signature
  • eSign Quiz
  • API
  • API Docs

Company

  • About
  • eSign Guide
  • Blog
  • Press
  • FAQ
  • Contact
Powered by eMudhra (CCA-licensed ESP)·IT Act 2000 Compliant·Aadhaar OTP Authenticated·Made in India 🇮🇳

© 2026 BN Habitat Pvt Ltd·CIN: U45400CH2010PTC043443·GST: 03AAECB5185C1Z3

Regd. Office: H.NO. 3355, 2nd Floor, Sector 37-D, Chandigarh, Chandigarh - 160036

Op. Office: Office 34, 13th Floor, Sushma Infinium, Chandigarh Ambala Expressway, Zirakpur, Punjab - 140603

TermsPrivacyRefundCookie