Java SDK
StampMitra Developer API का आधिकारिक Java क्लाइंट: fluent builder, immutable typed models, अपने आप retries और signed-webhook helpers के साथ identity verification और e-Stamp।
Pre-release 0.1.0 - package registry पर प्रकाशन जारी है
- Package
in.stampmitra:stampmitra-java- संस्करण
- 0.1.0
- आवश्यकता
- Java 11 या उससे नया (--release 11 के साथ build; JDK 11, 17 और 21 पर परीक्षित)। Runtime dependencies सिर्फ़ Jackson हैं (jackson-databind, jackson-datatype-jsr310)। HTTP के लिए JDK का built-in java.net.http.HttpClient इस्तेमाल होता है, और आप कोई दूसरा क्लाइंट भी लगा सकते हैं। Lombok या annotation processors नहीं हैं।
- लाइसेंस
- MIT
- Changelog
- CHANGELOG.md
- Immutable typed models; जो fields SDK नहीं जानता वे कभी नहीं छूटते (getRaw(), get("field"))।
- हर method का एक ...Async जोड़ीदार है जो CompletableFuture लौटाता है; cancellation और Thread.interrupt() का पालन होता है।
- Jitter वाले backoff के साथ retries, और webhook replay को छोड़कर हर POST पर अपने आप Idempotency-Key।
- Lazy log pagination (iterate, stream, pages), बदला जा सकने वाला HTTP transport और redacted debug logging।
Package इंस्टॉल करें
<dependency>
<groupId>in.stampmitra</groupId>
<artifactId>stampmitra-java</artifactId>
<version>0.1.0</version>
</dependency>implementation("in.stampmitra:stampmitra-java:0.1.0")प्रकाशित होने के बाद उपलब्ध। अभी Maven Central पर प्रकाशित नहीं हुआ है। यह कमांड package के रिलीज़ होने के बाद काम करेगा; तब तक रिलीज़ के लिए GitHub repository देखते रहें।
पाँच पंक्तियों में PAN verify करें
बनाते समय sandbox key (sk_test_...) इस्तेमाल करें। यह मुफ़्त है, आपके वॉलेट से कुछ नहीं कटता और sandbox डेटा पर चलती है।
import in.stampmitra.sdk.StampMitra;
import in.stampmitra.sdk.models.PanVerification;
StampMitra client = StampMitra.builder().apiKey(System.getenv("STAMPMITRA_API_KEY")).build();
PanVerification pan = client.verify().pan("ABCDE1234F", "Rahul Kumar");
System.out.println((pan.isValid() ? "valid " : "invalid ") + pan.getNameMatchResult() + " " + pan.getReferenceId());API keys और environments
Sandbox
Sandbox keys sk_test_ से शुरू होती हैं। ये मुफ़्त हैं और sandbox डेटा लौटाती हैं।
Live
Live keys sk_live_ से शुरू होती हैं। इनके लिए स्वीकृत production access request चाहिए और हर सफल कॉल पर शुल्क लगता है।
apiKey(...) न दें तो key STAMPMITRA_API_KEY environment variable से पढ़ी जाती है। client.environment() का मान "test" या "live" होता है। जो key ^sk_(test|live)_[A-Za-z0-9_-]{8,}$ से मेल नहीं खाती वह अस्वीकार होती है, जब तक आप allowCustomKey(true) न दें। baseUrl https:// होना चाहिए (plain http:// सिर्फ़ localhost, 127.0.0.1 और ::1 के लिए, जैसे mock server) और उसमें credentials नहीं होने चाहिए।
StampMitra client = StampMitra.builder()
.apiKey(apiKey)
.baseUrl("https://api-b2b.stampmitra.in")
.timeout(Duration.ofSeconds(30)) // per attempt
.maxRetries(2)
.appInfo("my-app", "1.0.0", "https://example.com")
.logger(line -> System.err.println("[stampmitra] " + line))
.validateInput(true)
.allowCustomKey(false)
.build();
// Also available: transport, defaultHeader, debug, executor (for the async methods), sleeper (tests).
// Every method also accepts per-request RequestOptions:
// timeout, maxRetries, idempotencyKey, header(name, value)हर resource एक जगह
ऑर्डर उस externalOrderId से पहचाने जाते हैं जो create लौटाता है (कोई id नहीं होती)। जो methods request class लेते हैं वे उन fields के लिए सादा Map<String, Object> भी लेते हैं जिन्हें SDK ने अभी model नहीं किया। जिन endpoints का आकार API ने तय नहीं किया (e-Stamp reference data, duty, parties, webhook events) वे GenericResponse लौटाते हैं, और GET /v1/services एक iterable ServiceList लौटाता है। Response का toString() व्यक्तिगत डेटा masked रखता है।
// Verification (POST /v1/verify/*). Every call, including valid=false results, is billed.
client.verify().pan("ABCDE1234F", "Rahul Kumar");
client.verify().gstin("27AABCT1332L1ZZ");
client.verify().rc("MH12AB1234");
client.verify().voterId("ABC1234567");
client.verify().drivingLicense("MH1420110012345", "1990-01-31"); // IP-allowlisted key required
client.verify().passport("PA1079341954215", "1990-01-31"); // IP-allowlisted key required
client.verify().cin("U74999DL2015PTC284009");
client.verify().udyam("UDYAM-MH-12-1234567");
client.verify().panToGstin("ABCDE1234F");
client.verify().panToUdyam("ABCDE1234F");
client.verify().bankAccount("000123456789", "ICIC0001234", "Rahul Kumar", null);
// Services and e-Stamp reference data
client.services().list();
client.services().get("estamp.instrument.rent_agreement");
client.estamp().getSchema("estamp.instrument.rent_agreement");
client.estamp().listStates();
client.estamp().listInstruments("MH");
client.estamp().listArticles("rent_agreement");
client.estamp().calculateDuty(EstampDutyCalculateRequest.builder()
.stateCode("MH").articleCode("5").stampDutyAmount(new BigDecimal("500")).build());
client.estamp().addParties("o1", EstampPartiesRequest.builder().customerName("A").partyName("B").build());
// Orders and payments (orders are addressed by the externalOrderId you supplied)
client.orders().create(CreateOrderRequest.builder()
.externalOrderId("o1").externalCustomerId("c1").serviceCode("estamp.instrument.rent_agreement").build());
client.orders().get("o1");
client.orders().getStatus("o1");
client.orders().getPayment("o1");
client.orders().listDocuments("o1");
client.orders().getDocumentDownload("o1", "doc_1"); // short-lived signed URL
client.payments().intents().create(CreatePaymentIntentRequest.builder()
.externalOrderId("o1").amountMinor(50000).currency("INR").paymentConnectionId("pc_1").build());
client.payments().intents().get("pi_1");
// Usage, logs, webhook deliveries
client.usage().summary();
client.logs().list(LogsParams.builder().service("pan.verify").status("success").limit(50).build());
client.webhooks().events().list(20, null);
client.webhooks().events().replay("evt_1");
// Every method has an Async twin returning a CompletableFuture
CompletableFuture<PanVerification> pan = client.verify().panAsync("ABCDE1234F");Pagination
iterate एक Iterable लौटाता है जो nextCursor को lazily आगे बढ़ाता है, stream एक java.util.stream.Stream लौटाता है और pages पूरे पेज iterate करता है; हर कॉल पहले पेज से शुरू होती है। list एक अकेला पेज लौटाता है।
LogsParams params = LogsParams.builder().service("pan.verify").limit(100).build();
for (LogEntry entry : client.logs().iterate(params)) {
System.out.println(entry.getId() + " " + entry.getCreatedAt());
}
LogPage page = client.logs().list(params); // page.getItems(), page.getNextCursor()
long firstFive = client.logs().stream(params).limit(5).count();Error classes
सभी errors unchecked हैं और in.stampmitra.sdk.errors.StampMitraException से बने हैं, जो getStatus(), getErrorCode(), getMessage(), getRequestId(), getField(), getBody() और getHeaders() देता है। RateLimitException में getRetryAfterSeconds() जुड़ता है (header न हो तो null)। बाकी statuses, जैसे अप्रत्याशित 3xx, base StampMitraException उठाते हैं, और non-JSON error body पर भी status से सही class मिलती है। Exceptions में API key कभी नहीं होती।
| HTTP status | Class | टिप्पणी |
|---|---|---|
| 400 / local validation | ValidationException | गलत इनपुट। Validation helpers इसे request भेजने से पहले local रूप से भी उठाते हैं। |
| 401 | AuthenticationException | API key न मिली, गलत है या रद्द हो चुकी है। |
| 402 | PaymentRequiredException | वॉलेट में बैलेंस या क्रेडिट कम है। |
| 403 | PermissionException | सर्वर का संदेश जस का तस दिया जाता है, जैसे Passport और Driving Licence के लिए IP-allowlisted key चाहिए। |
| 404 | NotFoundException | ऑर्डर, सेवा या इवेंट की id मौजूद नहीं है। |
| 409 | ConflictException | Idempotency-Key किसी दूसरी body के साथ दोबारा इस्तेमाल हुई, या state में टकराव है। |
| 429 | RateLimitException | रुककर दोबारा कोशिश करें; सर्वर Retry-After भेजे तो वह मान उपलब्ध रहता है। |
| 5xx | UpstreamException | सर्वर या upstream की ओर की विफलता। 502, 503 और 504 पर अपने आप retry होता है। |
| network / timeout | ConnectionException / RequestTimeoutException | कोई उपयोगी response नहीं मिला: कनेक्शन विफल, timeout या cancel। |
| webhook verification | SignatureVerificationException | गलत या पुराना webhook signature, खाली secret, या गलत timestamp। डिलीवरी को 400 से अस्वीकार करें। |
try {
client.verify().gstin("27AABCT1332L1ZZ");
} catch (RateLimitException e) {
// back off: e.getRetryAfterSeconds() may be null
} catch (StampMitraException e) {
System.err.println(e.getRequestId() + ": " + e.getMessage());
}Retries और idempotency
- डिफ़ॉल्ट maxRetries 2 है (अधिकतम 3 attempts), exponential backoff और full jitter के साथ (0.5 s base, 8 s cap)। सर्वर Retry-After भेजे तो उसका पालन होता है, अधिकतम 60 s तक। timeout (डिफ़ॉल्ट 30 s) हर attempt पर लागू होता है।
- Retry होते हैं: 429, 502, 503, 504, connection errors और timeouts। 429 के अलावा कोई भी 4xx कभी retry नहीं होता। GET हमेशा retry के लिए सुरक्षित है।
- SDK webhook replay को छोड़कर हर POST के लिए random UUIDv4 Idempotency-Key बनाता है और अपने आप होने वाले retries में वही key दोहराता है। API हर metered कॉल पर यह header मानती है, इसलिए retry हुआ verification दोबारा शुल्क लेने के बजाय पहला नतीजा लौटाता है। अपनी key RequestOptions से दें।
- बिना key वाला POST (webhook replay) तब तक retry नहीं होता जब तक आप key न दें।
क्लाइंट-साइड validation
भेजने से पहले इनपुट जाँचे जाते हैं (PAN, GSTIN, IFSC, CIN, Udyam, vehicle number, 9 से 18 अंकों का account number, passport file number, driving licence, voter ID, phone, ISO तारीखें, 256 अक्षरों तक के नाम), trim और upper-case होकर (account number सिर्फ़ trim होता है)। विफलता पर local ValidationException उठता है (getField() इनपुट का नाम देता है), बिना network call और बिना शुल्क के। Separators हटाए नहीं जाते, इसलिए MH-12 AB 1234 अस्वीकार होता है, जैसा API करती है। Validators सार्वजनिक हैं (Validators.pan(...)) और 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 पर नहीं।
Webhook.constructEvent, body को ठीक वैसी जैसी मिली उसी पर signature जाँचता है, तुलना constant-time में करता है, अभी से 300 s (बदला जा सकता है) से ज़्यादा दूर के timestamps और खाली secret को अस्वीकार करता है। विफलता पर SignatureVerificationException उठता है। Webhook.verifySignature boolean लौटाता है, और Webhook.constructEventFromHeaders तीनों headers किसी भी Map<String, ? extends Collection<String>> (जैसे com.sun.net.httpserver.Headers या servlet header map) से पढ़ लेता है।
import in.stampmitra.sdk.Webhook;
import in.stampmitra.sdk.models.WebhookEvent;
import in.stampmitra.sdk.models.WebhookEventType;
// rawBody: the request body as received (do not parse and re-serialise it)
WebhookEvent event = Webhook.constructEvent(rawBody, timestamp, signature, secret);
if (event.getType() == WebhookEventType.ORDER_PAYMENT_CONFIRMED) {
System.out.println(event.getCoreOrderId() + " paid at " + event.getOccurredAt());
}
boolean authentic = Webhook.verifySignature(rawBody, timestamp, signature, secret); // tolerance 300 sWebhookEvent आठ order.* events के लिए typed है (getType(), getCoreOrderId(), getCoreEventType(), getOccurredAt(), getEventId()); event के अपने अतिरिक्त fields सुरक्षित रहते हैं (event.get("..."), event.getRaw())। डिलीवरी retry होती है, इसलिए X-Webhook-Event-Id की event id से duplicates हटाएँ।
Framework के उदाहरण
import in.stampmitra.sdk.Webhook;
import in.stampmitra.sdk.errors.SignatureVerificationException;
import in.stampmitra.sdk.models.WebhookEvent;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
// Take the body as byte[]: the signature covers the raw bytes, so never let Spring parse the JSON first.
@RestController
public class WebhookController {
private final String secret;
public WebhookController(@Value("${stampmitra.webhook-secret}") String secret) {
this.secret = secret;
}
@PostMapping("/webhooks/stampmitra")
public ResponseEntity<String> receive(
@RequestBody byte[] rawBody,
@RequestHeader(Webhook.HEADER_TIMESTAMP) String timestamp,
@RequestHeader(Webhook.HEADER_SIGNATURE) String signature,
@RequestHeader(value = Webhook.HEADER_EVENT_ID, required = false) String eventId) {
try {
WebhookEvent event = Webhook.constructEvent(rawBody, timestamp, signature, secret,
Webhook.DEFAULT_TOLERANCE_SECONDS, eventId, System.currentTimeMillis() / 1000L);
System.out.println("verified " + event.getType() + " order=" + event.getCoreOrderId());
return ResponseEntity.ok("ok");
} catch (SignatureVerificationException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("invalid signature: " + e.getErrorCode());
}
}
}Webhooks: इवेंट के प्रकार, payload और retry नीति Webhooks पेज पर हैं।
सुरक्षित logging
डिफ़ॉल्ट रूप से कुछ भी log नहीं होता। हर request, response और retry की एक पंक्ति पाने के लिए logger(line -> ...) या debug(true) दें (यह in.stampmitra.sdk नाम के System.Logger पर DEBUG में लिखता है)। Authorization और x-api-key redact रहते हैं, identifiers के आख़िरी चार अक्षर रहते हैं, और नाम, जन्मतिथि व पते हटा दिए जाते हैं। API key log पंक्तियों, exception संदेशों, stack traces या toString() में कभी नहीं आती।
इंटीग्रेट करने के दूसरे तरीके
REST API किसी भी भाषा से चलती है, और बाकी SDKs भी उसी contract पर बने हैं।
API कॉल करने के लिए तैयार हैं?
मुफ़्त sandbox key लें और ऊपर का quickstart लगभग 5 मिनट में चलाएँ।