Developer Portal

Integrate GovSign in any language

One stable REST API. Native SDKs for C#, Node.js, Python, Java, Go and PHP, plus a drop-in JavaScript widget. Built for eBPS, banks, insurance companies and government offices — your application never holds keys, never wraps PAdES, never talks to OCSP/TSA/HSM. You call one method.

Quickstart → Desktop apps OpenAPI spec

How it works

Every signature follows the same shape, whoever the signer is:

The relying party never sees a private key. Keys live on the citizen's phone Secure Enclave, a USB token, a SIM applet, or a cloud HSM (AWS/Azure/GCP). GovSign servers only ever see a 32-byte hash and a short title.

Desktop downloads

For staff who sign with a USB token, or as a DSigner replacement. Verify against SHA256SUMS.txt.

🪟 Windows

Windows 10/11 · x64

.exe (67 MB)

🍎 macOS

macOS 12+ · Universal

.dmg (96 MB)

🐧 Debian/Ubuntu

amd64

.deb (76 MB)

🎩 RHEL/Fedora

x86_64

.rpm (95 MB)

Install an SDK

Pick your language. Every package exposes the same four operations.

.NET / C#
dotnet add package GovSign
Node.js / TypeScript
npm install @govsign/sdk
Python
pip install govsign
Java (Maven)
np.gov.govsign:govsign-sdk:1.0.9
Go
go get github.com/govnp/govsign-go
PHP (Composer)
composer require govsign/sdk
Browser widget
<script src="https://cdn.govsign.gov.np/widget/v1/govsign-widget.js"></script>
CLI
brew install govsign · apt install govsign

You'll receive a clientId + hmacSecret from the GovSign team (request via support). Keep the secret in your server environment, never in client code.

Quickstart by audience

Bank

KYC / account opening

Customer finishes the online KYC wizard. Your server renders the KYC PDF and asks GovSign to get it signed on the customer's phone. The PDF never leaves your server.

signPdf sample

Insurance

Claim filing

Policy holder signs the claim form remotely with a biometric tap. Returns a PAdES-LT PDF you archive for 7+ years.

signPdf sample

Government office

Bulk sealing (eBPS / eDMS)

Sign hundreds of tax notices, certificates or salary slips server-side under a department certificate — no human per document.

signPdfInstitutional sample

Citizen portal

One tap, many forms

Ask the citizen to sign a batch of documents with a single confirmation, or render a QR for desktop pairing.

signPdfBatch · createConnectSession

Code samples — every language

The four universal operations. Switch language with the tabs.

1 · Sign a PDF — citizen confirms on their phone

// dotnet add package GovSign — eBPS / bank / insurance
var gov = new GovSign("https://api.govsign.gov.np", clientId, hmacSecret);

byte[] signed = await gov.Signatures.SignPdfAsync(new SignPdfRequest {
    Pdf          = pdfBytes,
    CitizenId    = "12-34-56-7890",
    Title        = "Account Opening — KYC",
    RelyingParty = "NIBL Bank",
    Level        = "LT"          // B / T / LT (default) / LTA
});
// `signed` is a PAdES-LT PDF — store it, email it, archive it.
// npm install @govsign/sdk
import { GovSign } from "@govsign/sdk";
const gov = new GovSign({ baseUrl: "https://api.govsign.gov.np", clientId, hmacSecret: process.env.GOVSIGN_SECRET });

const signed = await gov.signatures.signPdf({
  pdf:          kycPdf,
  citizenId:    "12-34-56-7890",
  title:        "Account Opening — KYC",
  relyingParty: "NIBL Bank",
  level:        "LT",
});
// signed: Uint8Array — a PAdES-LT signed PDF
# pip install govsign
from govsign import GovSign
gov = GovSign(base_url="https://api.govsign.gov.np", client_id=client_id, hmac_secret=secret)

signed = gov.signatures.sign_pdf(
    pdf=claim_pdf,
    citizen_id="12-34-56-7890",
    title="Insurance claim #4821",
    relying_party="Nepal Life Insurance",
    level="LT",
)
# signed: bytes — PAdES-LT PDF
// np.gov.govsign:govsign-sdk:1.0.9
var gov = new GovSign("https://api.govsign.gov.np", clientId, hmacSecret);

byte[] signed = gov.signatures().signPdf(SignPdfRequest.builder()
    .pdf(pdfBytes)
    .citizenId("12-34-56-7890")
    .title("Loan application")
    .relyingParty("NIBL Bank")
    .level("LT")
    .build());
// go get github.com/govnp/govsign-go
gov := govsign.New("https://api.govsign.gov.np", clientID, hmacSecret)

signed, err := gov.Signatures.SignPdf(ctx, govsign.SignPdfRequest{
    Pdf:          pdfBytes,
    CitizenID:    "12-34-56-7890",
    Title:        "Tax clearance certificate",
    RelyingParty: "Inland Revenue Dept",
    Level:        "LT",
})
// composer require govsign/sdk
use GovSign\GovSign;
$gov = new GovSign("https://api.govsign.gov.np", $clientId, $secret);

$signed = $gov->signatures->signPdf([
    "pdf"          => $pdfBytes,
    "citizenId"    => "12-34-56-7890",
    "title"        => "Land registration deed",
    "relyingParty" => "Land Revenue Office",
    "level"        => "LT",
]);

2 · Sign many PDFs with one tap — signPdfBatch

const results = await gov.signatures.signPdfBatch({
  citizenId:    "12-34-56-7890",
  title:        "Quarterly filing — 4 forms",
  relyingParty: "eBPS",
  items: [
    { pdf: form1 }, { pdf: form2 }, { pdf: form3 }, { pdf: form4 },
  ],
});
// results: Uint8Array[] — one signed PDF per item, signed in a single confirmation
results = gov.signatures.sign_pdf_batch(
    citizen_id="12-34-56-7890",
    title="Quarterly filing — 4 forms",
    relying_party="eBPS",
    items=[{"pdf": f1}, {"pdf": f2}, {"pdf": f3}, {"pdf": f4}],
)
List<byte[]> results = gov.signatures().signPdfBatch(BatchRequest.builder()
    .citizenId("12-34-56-7890")
    .title("Quarterly filing")
    .relyingParty("eBPS")
    .add(form1).add(form2).add(form3)
    .build());
var results = await gov.Signatures.SignPdfBatchAsync(new BatchRequest {
    CitizenId    = "12-34-56-7890",
    Title        = "Quarterly filing",
    RelyingParty = "eBPS",
    Items        = { form1, form2, form3, form4 }
});

3 · Bulk sealing with a department certificate — signPdfInstitutional

Server-side, no citizen. The cert lives in a cloud HSM / file referenced by certReference — your app never handles key material.

# IRD seals 500 tax-clearance certificates overnight
result = gov.signatures.sign_pdf_institutional(
    cert_reference="ird-bulk-stamp-2026",   # resolved server-side to an HSM key
    level="LT",
    reason="Tax clearance",
    items=[{"pdf": pdf} for pdf in batch],
)
for item in result.signed_items:
    save(item.filename, item.signed_pdf)
const result = await gov.signatures.signPdfInstitutional({
  certReference: "ird-bulk-stamp-2026",
  level:         "LT",
  reason:        "Tax clearance",
  items:         batch.map(pdf => ({ pdf })),
});
result.signedItems.forEach(i => save(i.filename, i.signedPdfB64));
var result = gov.signatures().signPdfInstitutional(InstitutionalRequest.builder()
    .certReference("ird-bulk-stamp-2026")
    .level("LT").reason("Tax clearance")
    .items(batch)
    .build());
result, err := gov.Signatures.SignPdfInstitutional(ctx, govsign.InstitutionalRequest{
    CertReference: "ird-bulk-stamp-2026",
    Level:         "LT",
    Reason:        "Tax clearance",
    Items:         items,
})

4 · QR / desktop pairing — createConnectSession

const session = await gov.signatures.createConnectSession({
  documentDigest: sha256(pdf),
  title:          "Pension verification",
  relyingParty:   "Pension Management Office",
});
// Render session.deeplink as a QR. Poll session.id until state === "SIGNED".
renderQr(session.deeplink);            // govsign://connect?sid=…
showCode(session.verificationCode);    // 4-digit code the citizen confirms
session = gov.signatures.create_connect_session(
    document_digest=sha256(pdf),
    title="Pension verification",
    relying_party="Pension Management Office",
)
render_qr(session.deeplink)
show_code(session.verification_code)

Drop-in web widget — zero backend code

For a static HTML page or a portal with no server SDK. Add one script tag and a button. The widget calls your backend's /govsign/session endpoint to mint a signed request, then handles the phone/QR flow.

<script src="https://cdn.govsign.gov.np/widget/v1/govsign-widget.js"></script>

<button
    data-govsign
    data-citizen-id="12-34-56-7890"
    data-title="Grievance application"
    data-relying-party="Ward Office 12"
    data-pdf-url="/forms/grievance.pdf"
    data-on-signed="onSigned">
  Sign this form
</button>

<script>
  function onSigned(signedPdfUrl) { window.location = signedPdfUrl; }
</script>

Offline / USB-token signing (desktop bridge)

Staff with a hardware token use the GovSign desktop app. It exposes the same legacy WebSocket ports as DSigner (8080, 1645, 1812, 2083, 2948) and the same protocol verbs (signPdf, signForm, verifyPdf) — so existing eBPS / LMBIS integrations work unchanged. New integrations can also POST to the local bridge:

POST http://127.0.0.1:8080/sign   { pdfBase64, reason, location }
The desktop app signs with the PIN-protected token locally — the key never leaves the USB device. Install your token's PKCS#11 driver separately; GovSign auto-detects 24 driver families.

Validate a signature

const report = await gov.validations.validate(signedPdf);
if (report.valid) {
  console.log(report.signatures[0].signer, report.signatures[0].indication);
  // indication: TOTAL_PASSED | INDETERMINATE | TOTAL_FAILED  (ETSI EN 319 102-1)
}
report = gov.validations.validate(signed_pdf)
if report.valid:
    for s in report.signatures:
        print(s.signer, s.indication, s.ocsp_status)
curl -X POST https://api.govsign.gov.np/api/v1/validate/auto \
  -H "authorization: HMAC <clientId>:<ts>:<nonce>:<sig>" \
  --data-binary @signed.pdf
Validation fails closed: a revoked or untrusted certificate returns TOTAL_FAILED / INDETERMINATE, never a false "valid". OCSP + CRL are checked live.

Webhooks

Get notified when a signing session completes. Verify the signature the same way as Stripe — timing-safe.

app.post("/webhooks/govsign", (req, res) => {
  gov.webhooks.verify(req.rawBody, req.headers["x-govsign-signature"], webhookSecret);
  const evt = JSON.parse(req.rawBody);
  if (evt.type === "SIGNING_COMPLETED") markSigned(evt.sessionId);
  res.sendStatus(200);
});
@app.post("/webhooks/govsign")
def hook():
    gov.webhooks.verify(request.data, request.headers["X-GovSign-Signature"], webhook_secret)
    evt = request.get_json()
    if evt["type"] == "SIGNING_COMPLETED":
        mark_signed(evt["sessionId"])
    return "", 200
gov.webhooks().verify(rawBody, request.getHeader("X-GovSign-Signature"), webhookSecret);
// throws on bad signature / clock skew; otherwise proceed
$gov->webhooks->verify($rawBody, $_SERVER["HTTP_X_GOVSIGN_SIGNATURE"], $webhookSecret);

REST API reference

The SDKs call this stable contract. Auth on every call: Authorization: HMAC clientId:ts:nonce:sig. Full machine-readable spec: openapi.yaml (OpenAPI 3.1).

POST /api/v1/sign/pdf-citizen — open a citizen-confirm session → {sessionId, verificationCode}
GET  /api/v1/sign/pdf-citizen/{id} — poll until SIGNED
POST /api/v1/sign/batch-citizen — N PDFs, one tap
POST /api/v1/sign/bulk-institutional — server-side dept-cert sealing
POST /api/v1/validate/auto — auto-detect & validate any format
GET  /api/v1/citizens/{id} — enrolment / preferred channel
POST /connect/v1/session — QR-pair signing session (port 9096)

Request — open a citizen sign session

POST /api/v1/sign/pdf-citizen
{
  "idempotencyKey":    "a1b2c3…",
  "citizenId":         "12-34-56-7890",
  "title":             "Account Opening — KYC",
  "relyingParty":      "NIBL Bank",
  "documentDigestB64": "<sha-256 of the PDF, base64>",
  "pdfB64":            "<base64 PDF>",
  "level":             "LT"
}
→ 200  { "sessionId": "…", "verificationCode": "4821" }

Errors & status

HTTPMeaningWhat to do
200/201Success
400Bad request (missing field, invalid PDF)Fix the payload; check error code
401Auth failed (HMAC / nonce replay / clock skew)Check secret & clock (±120 s)
409Idempotency conflictSame key still in flight — poll the existing session
429Rate limitedBack off; default 120 req/min per client

All error bodies are a stable envelope: { "error": "CODE", "correlationId": "…" } — never a stack trace.

Full documentation

📘 User Manual

For staff & citizens: install, sign, validate, stamps, troubleshooting, FAQ.

Open →

🛠 Technical Docs

Architecture, services & ports, env vars, deployment (Docker/Helm), security model, runbook.

Open →

🔒 Security & Audit

Threat model, hardening (C1–C10, H1–H12, R-1…R-23), and what was verified.

Open →

Getting credentials & support