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.
How it works
Every signature follows the same shape, whoever the signer is:
- Your server keeps the PDF. The SDK hashes it locally — raw bytes never leave your network for the phone flows.
- The SDK HMAC-signs every request (Stripe-style) and opens a signing session.
- The citizen confirms on their phone (biometric), USB token, SIM Mobile-ID, or you sign server-side with a department certificate / cloud HSM.
- You get back a PAdES-LT signed PDF — Adobe-verifiable, EU eIDAS aligned, with embedded OCSP + trusted timestamp.
Desktop downloads
For staff who sign with a USB token, or as a DSigner replacement. Verify against SHA256SUMS.txt.
Install an SDK
Pick your language. Every package exposes the same four operations.
dotnet add package GovSignnpm install @govsign/sdkpip install govsignnp.gov.govsign:govsign-sdk:1.0.9go get github.com/govnp/govsign-gocomposer require govsign/sdk<script src="https://cdn.govsign.gov.np/widget/v1/govsign-widget.js"></script>brew install govsign · apt install govsignYou'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
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.
Claim filing
Policy holder signs the claim form remotely with a biometric tap. Returns a PAdES-LT PDF you archive for 7+ years.
Bulk sealing (eBPS / eDMS)
Sign hundreds of tax notices, certificates or salary slips server-side under a department certificate — no human per document.
One tap, many forms
Ask the citizen to sign a batch of documents with a single confirmation, or render a QR for desktop pairing.
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 confirmationresults = 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 confirmssession = 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:
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.pdfTOTAL_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 "", 200gov.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).
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
| HTTP | Meaning | What to do |
|---|---|---|
200/201 | Success | — |
400 | Bad request (missing field, invalid PDF) | Fix the payload; check error code |
401 | Auth failed (HMAC / nonce replay / clock skew) | Check secret & clock (±120 s) |
409 | Idempotency conflict | Same key still in flight — poll the existing session |
429 | Rate limited | Back off; default 120 req/min per client |
All error bodies are a stable envelope: { "error": "CODE", "correlationId": "…" } — never a stack trace.
Full documentation
🛠 Technical Docs
Architecture, services & ports, env vars, deployment (Docker/Helm), security model, runbook.
Open →Getting credentials & support
- Request a client (clientId + hmacSecret + certReference): email [email protected] with your office/company name and use-case.
- Sandbox:
https://sandbox.govsign.gov.np— test citizen IDs, no real signatures. - Production:
https://api.govsign.gov.np - Status & support: [email protected] · GitHub