StampMitraDevelopers
आधिकारिक SDK

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
इंस्टॉल

Package इंस्टॉल करें

Maven
<dependency>
  <groupId>in.stampmitra</groupId>
  <artifactId>stampmitra-java</artifactId>
  <version>0.1.0</version>
</dependency>
Gradle (Kotlin DSL)
implementation("in.stampmitra:stampmitra-java:0.1.0")

प्रकाशित होने के बाद उपलब्ध। अभी Maven Central पर प्रकाशित नहीं हुआ है। यह कमांड package के रिलीज़ होने के बाद काम करेगा; तब तक रिलीज़ के लिए GitHub repository देखते रहें।

Quickstart

पाँच पंक्तियों में PAN verify करें

बनाते समय sandbox key (sk_test_...) इस्तेमाल करें। यह मुफ़्त है, आपके वॉलेट से कुछ नहीं कटता और sandbox डेटा पर चलती है।

Java
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());
Authentication

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 नहीं होने चाहिए।

सिर्फ़ server-side। sk_ keys को कभी browser या mobile ऐप में न डालें और न ही किसी repository में commit करें। इन्हें environment variables या secrets manager में रखें। Authentication
Builder options
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)
Resources

हर 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 रखता है।

All resources
// 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

Pagination

iterate एक Iterable लौटाता है जो nextCursor को lazily आगे बढ़ाता है, stream एक java.util.stream.Stream लौटाता है और pages पूरे पेज iterate करता है; हर कॉल पहले पेज से शुरू होती है। list एक अकेला पेज लौटाता है।

Java
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();
Errors

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 statusClassटिप्पणी
400 / local validationValidationExceptionगलत इनपुट। Validation helpers इसे request भेजने से पहले local रूप से भी उठाते हैं।
401AuthenticationExceptionAPI key न मिली, गलत है या रद्द हो चुकी है।
402PaymentRequiredExceptionवॉलेट में बैलेंस या क्रेडिट कम है।
403PermissionExceptionसर्वर का संदेश जस का तस दिया जाता है, जैसे Passport और Driving Licence के लिए IP-allowlisted key चाहिए।
404NotFoundExceptionऑर्डर, सेवा या इवेंट की id मौजूद नहीं है।
409ConflictExceptionIdempotency-Key किसी दूसरी body के साथ दोबारा इस्तेमाल हुई, या state में टकराव है।
429RateLimitExceptionरुककर दोबारा कोशिश करें; सर्वर Retry-After भेजे तो वह मान उपलब्ध रहता है।
5xxUpstreamExceptionसर्वर या upstream की ओर की विफलता। 502, 503 और 504 पर अपने आप retry होता है।
network / timeoutConnectionException / RequestTimeoutExceptionकोई उपयोगी response नहीं मिला: कनेक्शन विफल, timeout या cancel।
webhook verificationSignatureVerificationExceptionगलत या पुराना webhook signature, खाली secret, या गलत timestamp। डिलीवरी को 400 से अस्वीकार करें।
Java
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

Retries और idempotency

Validation

क्लाइंट-साइड 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) से बंद किए जा सकते हैं।

Webhooks

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) से पढ़ लेता है।

Java
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 s

WebhookEvent आठ order.* events के लिए typed है (getType(), getCoreOrderId(), getCoreEventType(), getOccurredAt(), getEventId()); event के अपने अतिरिक्त fields सुरक्षित रहते हैं (event.get("..."), event.getRaw())। डिलीवरी retry होती है, इसलिए X-Webhook-Event-Id की event id से duplicates हटाएँ।

Framework के उदाहरण

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

सुरक्षित 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() में कभी नहीं आती।

SDKs

इंटीग्रेट करने के दूसरे तरीके

REST API किसी भी भाषा से चलती है, और बाकी SDKs भी उसी contract पर बने हैं।

API कॉल करने के लिए तैयार हैं?

मुफ़्त sandbox key लें और ऊपर का quickstart लगभग 5 मिनट में चलाएँ।