Node.js / TypeScript SDK
StampMitra Developer API का आधिकारिक Node.js और TypeScript क्लाइंट: typed responses, अपने आप retries और signed-webhook helpers के साथ identity verification और e-Stamp।
Pre-release 0.1.0 - package registry पर प्रकाशन जारी है
- Package
@stampmitra/sdk- संस्करण
- 0.1.0
- आवश्यकता
- Node.js 18 या उससे नया (global fetch का उपयोग करता है)। Core Bun, Deno और Cloudflare Workers पर भी चलता है। कोई runtime dependency नहीं, ESM/CJS दोनों, पूरी तरह typed।
- लाइसेंस
- MIT
- Changelog
- CHANGELOG.md
- Typed responses; जो fields SDK नहीं जानता वे भी सुरक्षित रहते हैं।
- Jitter वाले backoff के साथ retries, और webhook replay को छोड़कर हर POST पर अपने आप Idempotency-Key।
- Node, Next.js और edge runtimes (WebCrypto) के लिए webhook verification।
- Cursor auto-pagination, raw-response access और redacted debug logging।
Package इंस्टॉल करें
npm install @stampmitra/sdkप्रकाशित होने के बाद उपलब्ध। अभी npm पर प्रकाशित नहीं हुआ है। यह कमांड package के रिलीज़ होने के बाद काम करेगा; तब तक रिलीज़ के लिए GitHub repository देखते रहें।
पाँच पंक्तियों में PAN verify करें
बनाते समय sandbox key (sk_test_...) इस्तेमाल करें। यह मुफ़्त है, आपके वॉलेट से कुछ नहीं कटता और sandbox डेटा पर चलती है।
import { StampMitra } from '@stampmitra/sdk';
const client = new StampMitra(); // reads STAMPMITRA_API_KEY
const res = await client.verify.pan({ pan: 'ABCDE1234F', name: 'Rahul Kumar' });
console.log(res.referenceId, res.valid, res.nameMatchResult);API keys और environments
Sandbox
Sandbox keys sk_test_ से शुरू होती हैं। ये मुफ़्त हैं और sandbox डेटा लौटाती हैं।
Live
Live keys sk_live_ से शुरू होती हैं। इनके लिए स्वीकृत production access request चाहिए और हर सफल कॉल पर शुल्क लगता है।
apiKey दें या STAMPMITRA_API_KEY environment variable सेट करें। client.environment का मान 'test' या 'live' होता है। जो key ^sk_(test|live)_[A-Za-z0-9_-]{8,}$ से मेल नहीं खाती वह अस्वीकार होती है, जब तक आप allowCustomKey: true न दें। Browser runtime में constructor sk_live_ keys को अस्वीकार करता है और test keys पर चेतावनी देता है।
new StampMitra({
apiKey,
baseUrl: 'https://api-b2b.stampmitra.in',
timeout: 30_000,
maxRetries: 2,
fetch,
defaultHeaders,
appInfo: { name: 'my-app', version: '1.0.0', url: 'https://example.com' },
logger,
validateInput: true,
allowCustomKey: false,
});
// Every method also takes per-request options as its last argument:
// { timeout, maxRetries, idempotencyKey, headers, signal }हर resource एक जगह
Response bodies API के उदाहरणों से typed हैं। जिन payloads का आकार API ने अभी तय नहीं किया (e-Stamp details, duty, parties, payment intent) वे खुले objects के रूप में typed हैं।
// Verification (POST /v1/verify/*). Every call, including negative results, is billed.
await client.verify.pan({ pan: 'ABCDE1234F' });
await client.verify.gstin({ gstin: '07AAAAA1111A1Z1', businessName: 'Jane Doe Enterprises' });
await client.verify.rc({ vehicleNumber: 'MH12AB1234' });
await client.verify.voterId({ voterId: 'ABC1234567' });
await client.verify.drivingLicense({ dlNumber: 'MH1420110012345', dob: '1990-01-01' }); // IP-allowlisted key
await client.verify.passport({ fileNumber: 'PA1079341954215', dob: '1990-01-01' }); // IP-allowlisted key
await client.verify.cin({ cin: 'U74999DL2015PTC284009' });
await client.verify.udyam({ udyamNumber: 'UDYAM-MH-12-1234567' });
await client.verify.panToGstin({ pan: 'ABCDE1234F' });
await client.verify.panToUdyam({ pan: 'ABCDE1234F' });
await client.verify.bankAccount({ accountNumber: '000123456789', ifsc: 'ICIC0001234', name: 'Ravi Kumar' });
// Services and e-Stamp reference data
await client.services.list();
await client.services.get('estamp.instrument.rent_agreement');
await client.estamp.getSchema('estamp.instrument.rent_agreement');
await client.estamp.listStates();
await client.estamp.listInstruments('MH');
await client.estamp.listArticles('INSTRUMENT_CODE');
await client.estamp.calculateDuty({ /* see e-Stamp docs */ });
// Orders and payments
const order = await client.orders.create({
externalOrderId: 'o1',
externalCustomerId: 'c1',
serviceCode: 'estamp.instrument.rent_agreement',
details: {},
});
await client.estamp.addParties('ORDER_ID', { /* parties */ });
await client.orders.get('ORDER_ID');
await client.orders.getStatus('ORDER_ID');
await client.orders.getPayment('ORDER_ID');
await client.orders.listDocuments('ORDER_ID');
await client.orders.getDocumentDownload('ORDER_ID', 'DOCUMENT_ID'); // short-lived signed URL
await client.payments.intents.create({ /* see payment docs */ });
await client.payments.intents.get('INTENT_ID');
// Usage, logs, webhook deliveries
await client.usage.summary();
await client.logs.list({ service: 'estamp', status: 'failure', limit: 50 });
await client.webhooks.events.list({ limit: 20 });
await client.webhooks.events.replay('EVENT_ID');
// Raw response access
const { data, response } = await client.verify.pan({ pan: 'ABCDE1234F' }).withResponse();
console.log(response.status, response.requestId, response.attempts);Pagination
listAll आपके लिए nextCursor को आगे बढ़ाता है; list items और nextCursor वाला एक पेज लौटाता है।
for await (const entry of client.logs.listAll({ service: 'estamp' })) console.log(entry.id);
const page = await client.logs.list({ limit: 50 }); // page.items, page.nextCursor
for await (const delivery of client.webhooks.events.listAll()) {
// ...
}Error classes
हर error StampMitraError से बना है और status, code, message, requestId व raw देता है। ValidationError में field और RateLimitError में retryAfter (सेकंड) जुड़ता है। Errors सिर्फ़ HTTP status से map होते हैं, इसलिए non-JSON error body पर भी सही class मिलती है।
| HTTP status | Class | टिप्पणी |
|---|---|---|
| 400 / 422 | ValidationError | गलत इनपुट। Validation helpers इसे request भेजने से पहले local रूप से भी उठाते हैं। |
| 401 | AuthenticationError | API key न मिली, गलत है या रद्द हो चुकी है। |
| 402 | PaymentRequiredError | वॉलेट में बैलेंस या क्रेडिट कम है। |
| 403 | PermissionError | सर्वर का संदेश जस का तस दिया जाता है, जैसे Passport और Driving Licence के लिए IP-allowlisted key चाहिए। |
| 404 | NotFoundError | ऑर्डर, सेवा या इवेंट की id मौजूद नहीं है। |
| 409 | ConflictError | Idempotency-Key किसी दूसरी body के साथ दोबारा इस्तेमाल हुई, या state में टकराव है। |
| 429 | RateLimitError | रुककर दोबारा कोशिश करें; सर्वर Retry-After भेजे तो वह मान उपलब्ध रहता है। |
| 5xx | UpstreamError | सर्वर या upstream की ओर की विफलता। 502, 503 और 504 पर अपने आप retry होता है। |
| network / timeout / abort | ConnectionError / TimeoutError / RequestAbortedError | कोई उपयोगी response नहीं मिला: कनेक्शन विफल, timeout या cancel। |
import { ValidationError, RateLimitError } from '@stampmitra/sdk';
try {
await client.verify.pan({ pan: 'ABCDE1234F' });
} catch (e) {
if (e instanceof ValidationError) console.error('Bad input:', e.field, e.message);
else if (e instanceof RateLimitError) console.log(e.retryAfter, e.requestId);
else throw e;
}Retries और idempotency
- डिफ़ॉल्ट maxRetries 2 है, exponential backoff और full jitter के साथ (base 0.5 s, cap 8 s)। सर्वर Retry-After भेजे तो उसका पालन होता है।
- Retry होते हैं: 429, 502, 503, 504, connection errors और timeouts। GET हमेशा retry के लिए सुरक्षित है।
- Webhook replay को छोड़कर हर POST (सभी verification calls, estamp.calculateDuty, estamp.addParties, orders.create और payments.intents.create) अपने आप Idempotency-Key भेजता है (random UUIDv4, या आपकी अपनी idempotencyKey) और अपने आप होने वाले retries में वही key दोहराता है। API हर metered call पर key का सम्मान करता है, इसलिए retry हुआ verification दोबारा शुल्क के बजाय replay होता है।
- webhooks.events.replay में अपने आप key नहीं लगती और response या timeout के बाद वह retry नहीं होता; वह सिर्फ़ तब retry होता है जब किसी response से पहले कनेक्शन विफल हुआ हो। स्पष्ट idempotencyKey देने पर उसके लिए पूरा retry चालू हो जाता है।
क्लाइंट-साइड validation
इनपुट trim और upper-case होकर API के formats के विरुद्ध local रूप से जाँचे जाते हैं (PAN, GSTIN, IFSC, CIN, Udyam, vehicle number, 9 से 18 अंकों का bank account, passport file number, driving licence, voter ID, YYYY-MM-DD तारीखें, नाम की लंबाई)। गलत इनपुट पर ValidationError (local: true) उठता है, बिना network call और बिना शुल्क के। इसे validateInput: false से बंद करें।
Webhook signatures जाँचें
हर डिलीवरी में X-Webhook-Signature, X-Webhook-Timestamp (Unix सेकंड) और X-Webhook-Event-Id होते हैं। Signature, timestamp, एक बिंदु और raw body का hex HMAC-SHA256 है, जो आपके endpoint secret से बनता है। जाँच हमेशा raw body पर करें, दोबारा serialise किए गए JSON पर नहीं।
दो entry points हैं: @stampmitra/sdk/webhooks async है और WebCrypto का उपयोग करता है (Next.js, Cloudflare Workers, Node 18+), जबकि @stampmitra/sdk/webhooks-node synchronous Node संस्करण है। पुराने timestamps (डिफ़ॉल्ट tolerance 300 s) अस्वीकार होते हैं और तुलना constant-time में होती है।
// Edge runtimes, Next.js, Node 18+ (async, WebCrypto)
import { constructEventFromHeaders } from '@stampmitra/sdk/webhooks';
const event = await constructEventFromHeaders(rawBody, request.headers, secret);
// Synchronous Node variant
import { verifySignature, constructEvent } from '@stampmitra/sdk/webhooks-node';
const ok = verifySignature(rawBody, timestampHeader, signatureHeader, secret); // booleanverifySignature boolean लौटाता है और गलत इनपुट पर कभी exception नहीं उठाता। constructEvent WebhookSignatureError उठाता है, जिसका reason malformed, stale या mismatch होता है।
Framework के उदाहरण
import express, { type Request, type Response } from 'express';
import { constructEvent, WebhookSignatureError } from '@stampmitra/sdk/webhooks-node';
const app = express();
// IMPORTANT: verify against the RAW body, not re-serialised JSON.
app.post(
'/webhooks/stampmitra',
express.raw({ type: 'application/json' }),
(req: Request, res: Response) => {
try {
const event = constructEvent(
req.body as Buffer,
String(req.header('X-Webhook-Timestamp')),
String(req.header('X-Webhook-Signature')),
process.env.STAMPMITRA_WEBHOOK_SECRET!,
);
console.log('event', req.header('X-Webhook-Event-Id'), event);
res.sendStatus(200);
} catch (err) {
if (err instanceof WebhookSignatureError) return void res.status(400).send(err.reason);
throw err;
}
},
);
app.listen(3000);Webhooks: इवेंट के प्रकार, payload और retry नीति Webhooks पेज पर हैं।
सुरक्षित logging
डिफ़ॉल्ट रूप से कुछ भी log नहीं होता। Request और response के records पाने के लिए logger: { debug(msg, meta) {} } दें; इनमें Authorization और x-api-key redact रहते हैं और PAN, GSTIN, account numbers व नाम masked रहते हैं।
इंटीग्रेट करने के दूसरे तरीके
REST API किसी भी भाषा से चलती है, और बाकी SDKs भी उसी contract पर बने हैं।
API कॉल करने के लिए तैयार हैं?
मुफ़्त sandbox key लें और ऊपर का quickstart लगभग 5 मिनट में चलाएँ।