.NET SDK
StampMitra Developer API का आधिकारिक .NET क्लाइंट: typed records, IAsyncEnumerable pagination, अपने आप retries, signed-webhook helpers और ASP.NET Core dependency injection के साथ async-first identity verification और e-Stamp।
Pre-release 0.1.0 - package registry पर प्रकाशन जारी है
- Package
StampMitra- संस्करण
- 0.1.0
- आवश्यकता
- net6.0 या net8.0 (और इसलिए हर बाद का runtime)। netstandard2.1 या .NET Framework build नहीं है। Core package सिर्फ़ System.Net.Http.HttpClient और System.Text.Json इस्तेमाल करता है, कोई third-party runtime dependency नहीं। C# 9 या नया सुझाया जाता है (records और init-only properties)। .NET 6 का support समाप्त हो चुका है और मौजूदा applications के लिए target के रूप में रखा गया है।
- लाइसेंस
- MIT
- Changelog
- CHANGELOG.md
- Async-first, हर कॉल पर CancellationToken; cancellation कभी wrap नहीं होता।
- Immutable records; अज्ञात fields AdditionalProperties में रखे जाते हैं।
- Jitter वाले backoff के साथ retries, और webhook replay को छोड़कर हर POST पर अपने आप Idempotency-Key।
- StampMitra.Extensions.DependencyInjection में services.AddStampMitra(...); core package में Microsoft.Extensions पर कोई dependency नहीं।
Package इंस्टॉल करें
dotnet add package StampMitra
# optional: services.AddStampMitra(...) for ASP.NET Core / generic host
dotnet add package StampMitra.Extensions.DependencyInjectionप्रकाशित होने के बाद उपलब्ध। अभी NuGet पर प्रकाशित नहीं हुआ है। यह कमांड package के रिलीज़ होने के बाद काम करेगा; तब तक रिलीज़ के लिए GitHub repository देखते रहें।
पाँच पंक्तियों में PAN verify करें
बनाते समय sandbox key (sk_test_...) इस्तेमाल करें। यह मुफ़्त है, आपके वॉलेट से कुछ नहीं कटता और sandbox डेटा पर चलती है।
using StampMitra;
using StampMitra.Models;
using var client = new StampMitraClient(); // reads STAMPMITRA_API_KEY
var pan = await client.Verify.PanAsync(new PanVerifyRequest { Pan = "ABCDE1234F", Name = "Rahul Kumar" }, ct);
Console.WriteLine($"{(pan.Valid == true ? "valid" : "invalid")} {pan.NameMatchResult} {pan.ReferenceId}");API keys और environments
Sandbox
Sandbox keys sk_test_ से शुरू होती हैं। ये मुफ़्त हैं और sandbox डेटा लौटाती हैं।
Live
Live keys sk_live_ से शुरू होती हैं। इनके लिए स्वीकृत production access request चाहिए और हर सफल कॉल पर शुल्क लगता है।
StampMitraClientOptions.ApiKey न दें तो क्लाइंट STAMPMITRA_API_KEY पढ़ता है। जो key ^sk_(test|live)_[A-Za-z0-9_-]{8,}$ से मेल नहीं खाती वह अस्वीकार होती है, जब तक AllowCustomKey सेट न हो। client.Environment का मान Test, Live या Custom होता है। BaseUrl https होना चाहिए (loopback http सिर्फ़ mock servers के लिए) और URL में credentials नहीं होने चाहिए। API key ToString(), exceptions या logs में कभी नहीं आती। उदाहरणों में ct एक CancellationToken है; cancellation न चाहिए तो CancellationToken.None दें।
using var client = new StampMitraClient(new StampMitraClientOptions
{
ApiKey = Environment.GetEnvironmentVariable("STAMPMITRA_API_KEY"),
BaseUrl = "https://api-b2b.stampmitra.in",
Timeout = TimeSpan.FromSeconds(20), // per attempt, default 30 s
MaxRetries = 3, // default 2, range 0 to 10
AppInfo = new AppInfo("my-app", "1.4.0", "https://example.com"),
DebugLog = line => Console.Error.WriteLine(line),
ValidateInput = true,
AllowCustomKey = false,
});
// Also available: HttpClient (yours is never disposed), DefaultHeaders.
// Every method also takes a per-call RequestOptions (Timeout, MaxRetries, IdempotencyKey, Headers) and a CancellationToken.हर resource एक जगह
Responses immutable records हैं (pan.RegisteredName)। जो fields SDK नहीं जानता वे AdditionalProperties में रखे जाते हैं (नाम से JsonElement), और HTTP metadata (status, headers, request id, attempts) model.Response पर है। जिन responses का आकार API ने तय नहीं किया (e-Stamp reference data, duty, parties, webhook events) वे GenericResponse हैं (Data, Get(name), Items, NextCursor, RawJson), और GET /v1/services एक सादा array लौटाता है जो ServiceList के रूप में मिलता है।
using StampMitra.Models; // models and request types
using StampMitra.Resources; // LogsListParams
// Verification: every call is billed, including valid = false results
var pan = await client.Verify.PanAsync(new PanVerifyRequest { Pan = "ABCDE1234F", Name = "Rahul Kumar" }, ct);
var gst = await client.Verify.GstinAsync(new GstinVerifyRequest { Gstin = "27ABCDE1234F1Z5" }, ct);
var rc = await client.Verify.RcAsync(new VehicleRcVerifyRequest { VehicleNumber = "MH12AB1234" }, ct);
var bank = await client.Verify.BankAccountAsync(new BankAccountVerifyRequest { AccountNumber = "000123456789", Ifsc = "ICIC0001234", Name = "Rahul Kumar" }, ct);
// Also: VoterIdAsync, DrivingLicenseAsync* , PassportAsync* , CinAsync, UdyamAsync, PanToGstinAsync, PanToUdyamAsync
// * needs an IP-allowlisted key; otherwise PermissionDeniedException carries the server message
// Services and e-Stamp reference data
ServiceList services = await client.Services.ListAsync(ct);
GenericResponse states = await client.Estamp.ListStatesAsync(ct);
GenericResponse duty = await client.Estamp.CalculateDutyAsync(new EstampDutyCalculateRequest { StateCode = "MH", ArticleCode = "5", StampDutyAmount = 500 }, ct);
// Also: Services.GetAsync, Estamp.GetSchemaAsync, ListInstrumentsAsync(stateCode), ListArticlesAsync(instrumentCode), AddPartiesAsync(orderId, ...)
// Orders and payments (addressed by the externalOrderId you supplied)
var order = await client.Orders.CreateAsync(
new CreateOrderRequest { ExternalOrderId = "o1", ExternalCustomerId = "c1", ServiceCode = "estamp.instrument.rent_agreement" },
new RequestOptions { IdempotencyKey = "order-o1", Timeout = TimeSpan.FromSeconds(10) },
ct);
OrderStatusResponse status = await client.Orders.GetStatusAsync(order.ExternalOrderId!, ct);
// Also: Orders.GetAsync, GetPaymentAsync, ListDocumentsAsync, GetDocumentDownloadAsync(id, documentId), Payments.Intents.CreateAsync / GetAsync
// Usage, logs, webhook deliveries
UsageSummary usage = await client.Usage.SummaryAsync(ct);
LogsPage logs = await client.Logs.ListAsync(new LogsListParams { Status = "failure", Limit = 50 }, ct);
GenericResponse events = await client.Webhooks.Events.ListAsync(20, null, null, ct);
// Also: Webhooks.Events.ReplayAsyncPagination
ListAllAsync एक IAsyncEnumerable<LogEntry> लौटाता है जो nextCursor को lazily आगे बढ़ाता है; ListAsync Items और NextCursor वाला एक पेज लौटाता है।
await foreach (var entry in client.Logs.ListAllAsync(new LogsListParams { Service = "pan.verify", Limit = 100 }, null, ct))
{
Console.WriteLine($"{entry.Id} {entry.CreatedAt}"); // follows nextCursor lazily
}
LogsPage page = await client.Logs.ListAsync(new LogsListParams { Status = "failure", Limit = 50 }, ct); // page.Items, page.NextCursorError classes
सभी errors StampMitraException से बने हैं, जो Status, Code, Message, RequestId, Field और Body देता है। Header मौजूद हो तो RateLimitException में RetryAfter जुड़ता है। कोई भी दूसरा status base StampMitraException उठाता है, और non-JSON error body पर भी status से सही class मिलती है। कॉलर का cancellation OperationCanceledException के रूप में आता है, कभी wrap नहीं होता।
| HTTP status | Class | टिप्पणी |
|---|---|---|
| 400 / local validation | ValidationException | गलत इनपुट। Validation helpers इसे request भेजने से पहले local रूप से भी उठाते हैं। |
| 401 | AuthenticationException | API key न मिली, गलत है या रद्द हो चुकी है। |
| 402 | PaymentRequiredException | वॉलेट में बैलेंस या क्रेडिट कम है। |
| 403 | PermissionDeniedException | सर्वर का संदेश जस का तस दिया जाता है, जैसे 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 | ApiConnectionException / ApiTimeoutException | कोई उपयोगी response नहीं मिला: कनेक्शन विफल, timeout या cancel। |
| webhook verification | SignatureVerificationException | गलत या पुराना webhook signature, खाली secret, या गलत timestamp। डिलीवरी को 400 से अस्वीकार करें। |
try
{
await client.Verify.GstinAsync(new GstinVerifyRequest { Gstin = "27ABCDE1234F1Z5" }, ct);
}
catch (RateLimitException ex)
{
Console.WriteLine($"back off {ex.RetryAfter}");
}
catch (StampMitraException ex)
{
Console.Error.WriteLine($"{ex.RequestId}: {ex.Message}");
}Retries और idempotency
- MaxRetries का डिफ़ॉल्ट 2 है, exponential backoff और full jitter के साथ (0.5 s base, 8 s cap)। Retry-After भेजा जाए तो उसका पालन होता है, अधिकतम 60 s तक। Timeout (डिफ़ॉल्ट 30 s) हर attempt पर लागू होता है, इसलिए timeout हुआ attempt ApiTimeoutException उठाता है और retry-safe होने पर retry होता है।
- Retry होते हैं: 429, 502, 503, 504, connection errors और timeouts। बाकी statuses (400, 401, 402, 403, 404, 409, 500) कभी retry नहीं होते। GET हमेशा retry के लिए सुरक्षित है।
- POST तभी retry होता है जब उसमें Idempotency-Key हो। SDK webhook replay को छोड़कर हर POST (verification, duty, parties, orders, payment intents) के लिए random UUIDv4 (operating system CSPRNG से) बनाता है और retries में वही key दोहराता है, इसलिए retry हुआ metered कॉल दोबारा शुल्क के बजाय पहला नतीजा लौटाता है। अपनी key RequestOptions.IdempotencyKey से दें।
- Webhook replay तब तक retry नहीं होता जब तक आप key न दें। अपना HttpClient दें तो उसे AllowAutoRedirect = false और Timeout = Timeout.InfiniteTimeSpan के साथ बनाएँ (DI package दोनों करता है); SDK timeout हर attempt पर खुद लागू करता है और redirects कभी नहीं मानता।
क्लाइंट-साइड validation
भेजने से पहले इनपुट जाँचे जाते हैं (PAN, GSTIN, IFSC, CIN, Udyam, vehicle number, 9 से 18 अंकों का account number, passport file number, driving licence, voter ID, phone, ISO तारीखें), जहाँ API करती है वहाँ trim और upper-case होकर। विफलता पर local ValidationException उठता है (Field सेट होता है), बिना network call और बिना शुल्क के। MH-12 AB 1234 जैसे separators हटाए नहीं जाते, ठीक 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 पर नहीं।
WebhookSignature.ConstructEvent आठों events में से हर एक के लिए typed record लौटाता है (Type दस्तावेज़ित नाम है, जैसे order.created) या UnknownWebhookEvent; अतिरिक्त fields AdditionalProperties में रखे जाते हैं। तुलना constant-time में होती है, अभी से 300 s (बदला जा सकता है) से ज़्यादा दूर के timestamps अस्वीकार होते हैं, खाली secret अस्वीकार होता है, और विफलता पर SignatureVerificationException उठता है (Code: SIGNATURE_MISMATCH, INVALID_TIMESTAMP, TIMESTAMP_OUTSIDE_TOLERANCE, INVALID_PAYLOAD, MISSING_HEADERS या MISSING_SECRET)। WebhookSignature.Verify bool लौटाता है, और ConstructEventFromHeaders तीनों headers एक lookup से पढ़ता है।
using StampMitra.Webhooks;
// rawBody: the request body bytes, untouched (never parse and re-serialise)
WebhookEvent evt = WebhookSignature.ConstructEvent(rawBody, timestamp, signature, secret, eventId: eventId);
switch (evt)
{
case OrderPaymentConfirmedEvent paid:
Console.WriteLine($"paid: {paid.CoreOrderId}");
break;
case OrderDocumentReadyEvent ready:
Console.WriteLine($"document ready: {ready.CoreOrderId}");
break;
}
bool authentic = WebhookSignature.Verify(rawBody, timestamp, signature, secret, toleranceSeconds: 300);प्लेटफ़ॉर्म डिलीवरी retry करता है, इसलिए कोई भी काम करने से पहले EventId (X-Webhook-Event-Id से) पर duplicates हटाएँ।
Framework के उदाहरण
using StampMitra;
using StampMitra.Webhooks;
var secret = Environment.GetEnvironmentVariable("STAMPMITRA_WEBHOOK_SECRET");
if (string.IsNullOrEmpty(secret))
{
Console.Error.WriteLine("Set STAMPMITRA_WEBHOOK_SECRET to the endpoint's signing secret.");
return 1;
}
var app = WebApplication.CreateBuilder(args).Build();
app.MapPost("/webhooks/stampmitra", async (HttpRequest request, ILogger<Program> logger) =>
{
// The signature covers the raw request bytes: read the body untouched, never re-serialise it.
using var ms = new MemoryStream();
await request.Body.CopyToAsync(ms);
var raw = ms.ToArray();
WebhookEvent evt;
try
{
evt = WebhookSignature.ConstructEventFromHeaders(raw, name => request.Headers[name].FirstOrDefault(), secret);
}
catch (SignatureVerificationException ex)
{
logger.LogWarning("Rejected webhook: {Code}", ex.Code);
return Results.BadRequest(new { error = "invalid signature" });
}
// Deliveries are retried: de-duplicate on evt.EventId before doing any work.
switch (evt)
{
case OrderPaymentConfirmedEvent paid:
logger.LogInformation("Payment confirmed for order {Order}", paid.CoreOrderId);
break;
case OrderDocumentReadyEvent ready:
logger.LogInformation("Document ready for order {Order}", ready.CoreOrderId);
break;
default:
logger.LogInformation("{Type} for order {Order} (event {EventId})", evt.Type ?? evt.CoreEventType, evt.CoreOrderId, evt.EventId);
break;
}
return Results.Ok(new { received = true });
});
await app.RunAsync();
return 0;
// Makes ILogger<Program> resolvable in a top-level-statements program.
public partial class Program
{
}Webhooks: इवेंट के प्रकार, payload और retry नीति Webhooks पेज पर हैं।
सुरक्षित logging
डिफ़ॉल्ट रूप से कुछ भी log नहीं होता। हर request और response की एक पंक्ति पाने के लिए DebugLog (एक Action<string>) सेट करें। Authorization और x-api-key redact रहते हैं, identifiers (PAN, GSTIN, account number आदि) आख़िरी चार अक्षरों तक masked रहते हैं और नाम व जन्मतिथि हटा दी जाती हैं; वही masking कहीं और चाहिए तो Redactor सार्वजनिक है। DI package के साथ ये पंक्तियाँ ILogger पर Debug level में जाती हैं। फिर भी logs को संवेदनशील मानें।
इंटीग्रेट करने के दूसरे तरीके
REST API किसी भी भाषा से चलती है, और बाकी SDKs भी उसी contract पर बने हैं।
API कॉल करने के लिए तैयार हैं?
मुफ़्त sandbox key लें और ऊपर का quickstart लगभग 5 मिनट में चलाएँ।