Go SDK
StampMitra Developer API का आधिकारिक Go क्लाइंट: context-aware कॉल, typed responses, errors.Is के अनुकूल errors, अपने आप retries और signed-webhook helpers के साथ identity verification और e-Stamp, सिर्फ़ standard library से।
Pre-release 0.1.0 - package registry पर प्रकाशन जारी है
- Package
github.com/Bani-Global-Industries-LLP/stampmitra-developer-go- संस्करण
- 0.1.0
- आवश्यकता
- Go 1.21 या उससे नया (CI 1.21 से 1.24 पर चलता है)। सिर्फ़ standard library: कोई third-party dependency नहीं। Package का नाम stampmitra है, और module path case-sensitive है, इसलिए इसे दिखाए गए बड़े अक्षरों के साथ ही import करें।
- लाइसेंस
- MIT
- Changelog
- CHANGELOG.md
- सिर्फ़ standard library; हर कॉल context.Context और per-call options लेती है।
- Responses अज्ञात fields को Extra में रखते हैं, इसलिए API जो भेजे वह खोता नहीं।
- Jitter वाले backoff के साथ retries, और webhook replay को छोड़कर हर POST पर अपने आप Idempotency-Key।
- Lazy log iterator, typed webhook events, और log/slog के ज़रिए redacted debug logging।
Package इंस्टॉल करें
go get github.com/Bani-Global-Industries-LLP/[email protected]प्रकाशित होने के बाद उपलब्ध। अभी the Go module proxy पर प्रकाशित नहीं हुआ है। यह कमांड package के रिलीज़ होने के बाद काम करेगा; तब तक रिलीज़ के लिए GitHub repository देखते रहें।
पाँच पंक्तियों में PAN verify करें
बनाते समय sandbox key (sk_test_...) इस्तेमाल करें। यह मुफ़्त है, आपके वॉलेट से कुछ नहीं कटता और sandbox डेटा पर चलती है।
import stampmitra "github.com/Bani-Global-Industries-LLP/stampmitra-developer-go"
client, err := stampmitra.NewClient("") // empty = read STAMPMITRA_API_KEY
if err != nil { log.Fatal(err) }
pan, err := client.Verify.PAN(context.Background(), &stampmitra.PANVerifyParams{PAN: "ABCDE1234F", Name: "Rahul Kumar"})
if err != nil { log.Fatal(err) }
fmt.Println(pan.Valid, pan.NameMatchResult, pan.ReferenceID)API keys और environments
Sandbox
Sandbox keys sk_test_ से शुरू होती हैं। ये मुफ़्त हैं और sandbox डेटा लौटाती हैं।
Live
Live keys sk_live_ से शुरू होती हैं। इनके लिए स्वीकृत production access request चाहिए और हर सफल कॉल पर शुल्क लगता है।
NewClient("") STAMPMITRA_API_KEY environment variable पढ़ता है। जो key ^sk_(test|live)_[A-Za-z0-9_-]{8,}$ से मेल नहीं खाती वह अस्वीकार होती है, जब तक आप WithAllowCustomKey() न दें। client.Environment का मान "test", "live" या "custom" होता है। Base URL https होना चाहिए (plain http सिर्फ़ localhost, 127.0.0.1 और ::1 के लिए), उसमें credentials और query नहीं होने चाहिए। API key क्लाइंट के fmt output (%v, %+v, %#v), errors या logs में कभी नहीं आती।
client, err := stampmitra.NewClient(key,
stampmitra.WithBaseURL("https://api-b2b.stampmitra.in"),
stampmitra.WithTimeout(30*time.Second), // per attempt
stampmitra.WithMaxRetries(2),
stampmitra.WithBackoff(500*time.Millisecond, 8*time.Second),
stampmitra.WithHTTPClient(httpClient), // redirects are still never followed
stampmitra.WithDefaultHeader("X-Trace", "abc"),
stampmitra.WithAppInfo(stampmitra.AppInfo{Name: "my-app", Version: "1.0.0"}),
stampmitra.WithLogger(slog.Default()),
stampmitra.WithValidateInput(true),
stampmitra.WithAllowCustomKey(),
)
// Every method takes a context.Context first and per-call options last:
// WithRequestTimeout, WithRequestMaxRetries, WithIdempotencyKey, WithRequestHeader, WithRawResponseहर resource एक जगह
Responses json tags वाले structs हैं (pan.RegisteredName)। हर response struct में उन fields के लिए Extra map[string]json.RawMessage है जिन्हें इस SDK संस्करण ने model नहीं किया, और json.Marshal उन्हें वापस लिख देता है। जिन endpoints का आकार API ने तय नहीं किया (e-Stamp reference data, duty, parties, webhook event lists और replays) वे *GenericResponse लौटाते हैं (Raw, Decode, Get, Map, Items, NextCursor)। ऑर्डर उस externalOrderId से पहचाने जाते हैं जो Create लौटाता है (कोई id नहीं होती)।
ctx := context.Background()
// Verification (every call, including valid=false results, is billed)
pan, err := client.Verify.PAN(ctx, &stampmitra.PANVerifyParams{PAN: "ABCDE1234F", Name: "Rahul Kumar"})
gst, err := client.Verify.GSTIN(ctx, &stampmitra.GSTINVerifyParams{GSTIN: "27ABCDE1234F1Z5"})
rc, err := client.Verify.RC(ctx, &stampmitra.RCVerifyParams{VehicleNumber: "MH12AB1234"})
voter, err := client.Verify.VoterID(ctx, &stampmitra.VoterIDVerifyParams{VoterID: "ABC1234567"})
dl, err := client.Verify.DrivingLicense(ctx, &stampmitra.DrivingLicenseVerifyParams{DLNumber: "MH1420110012345", DOB: "1990-01-31"}) // IP-allowlisted key
pp, err := client.Verify.Passport(ctx, &stampmitra.PassportVerifyParams{FileNumber: "PA1079341954215", DOB: "1990-01-31"}) // IP-allowlisted key
cin, err := client.Verify.CIN(ctx, &stampmitra.CINVerifyParams{CIN: "U74999DL2015PTC284009"})
udyam, err := client.Verify.Udyam(ctx, &stampmitra.UdyamVerifyParams{UdyamNumber: "UDYAM-MH-12-1234567"})
gstins, err := client.Verify.PANToGSTIN(ctx, &stampmitra.PANToGSTINParams{PAN: "ABCDE1234F"})
udyams, err := client.Verify.PANToUdyam(ctx, &stampmitra.PANToUdyamParams{PAN: "ABCDE1234F"})
bank, err := client.Verify.BankAccount(ctx, &stampmitra.BankAccountVerifyParams{AccountNumber: "000123456789", IFSC: "ICIC0001234", Name: "Rahul Kumar"})
// Services and e-Stamp reference data
services, err := client.Services.List(ctx)
service, err := client.Services.Get(ctx, "estamp.instrument.rent_agreement")
schema, err := client.Estamp.GetSchema(ctx, "estamp.instrument.rent_agreement")
states, err := client.Estamp.ListStates(ctx)
instruments, err := client.Estamp.ListInstruments(ctx, "MH")
articles, err := client.Estamp.ListArticles(ctx, "rent_agreement")
duty, err := client.Estamp.CalculateDuty(ctx, &stampmitra.EstampDutyParams{StateCode: "MH", ArticleCode: "5", StampDutyAmount: 500})
parties, err := client.Estamp.AddParties(ctx, "ord_ext_123", &stampmitra.EstampPartiesParams{CustomerName: "A", PartyName: "B"})
// Orders and payments: orders are addressed by YOUR externalOrderId
order, err := client.Orders.Create(ctx, &stampmitra.CreateOrderParams{
ExternalOrderID: "ord_ext_123", ExternalCustomerID: "cust_ext_456", ServiceCode: "estamp.instrument.rent_agreement",
}, stampmitra.WithIdempotencyKey("create-ord_ext_123"))
detail, err := client.Orders.Get(ctx, order.ExternalOrderID)
status, err := client.Orders.GetStatus(ctx, order.ExternalOrderID)
payment, err := client.Orders.GetPayment(ctx, order.ExternalOrderID)
docs, err := client.Orders.ListDocuments(ctx, order.ExternalOrderID)
link, err := client.Orders.GetDocumentDownload(ctx, order.ExternalOrderID, "doc_1") // link.DownloadURL is short-lived
intent, err := client.Payments.Intents.Create(ctx, &stampmitra.CreatePaymentIntentParams{ExternalOrderID: "ord_ext_123", AmountMinor: 50000, Currency: "INR", PaymentConnectionID: "pc_1"})
intent, err = client.Payments.Intents.Get(ctx, intent.ID)
// Usage, logs, webhook deliveries
usage, err := client.Usage.Summary(ctx)
page, err := client.Logs.List(ctx, &stampmitra.LogsListParams{Service: "pan.verify", Status: "success", Limit: 50})
events, err := client.Webhooks.Events.List(ctx, &stampmitra.WebhookEventsListParams{Limit: 20})
replay, err := client.Webhooks.Events.Replay(ctx, "evt_1")
// Raw HTTP metadata
var raw stampmitra.Response
st, err := client.Orders.GetStatus(ctx, "ord_ext_123", stampmitra.WithRawResponse(&raw))
fmt.Println(st != nil, err == nil, raw.StatusCode, raw.RequestID, raw.Attempts)Pagination
ListAll खुद कोई I/O नहीं करता; Next पेज lazily लाता है और nextCursor को आगे बढ़ाता है। एक पेज के लिए Logs.List इस्तेमाल करें। Webhooks.Events.List एक *GenericResponse लौटाता है जिसमें Items() और NextCursor() हैं।
it := client.Logs.ListAll(&stampmitra.LogsListParams{Service: "pan.verify", Limit: 100})
for it.Next(ctx) {
entry := it.Log()
fmt.Println(entry.ID, entry.CreatedAt)
}
if err := it.Err(); err != nil {
log.Fatal(err)
}
page, err := client.Logs.List(ctx, &stampmitra.LogsListParams{Limit: 50}) // page.Items, page.NextCursorError classes
हर non-2xx response एक *stampmitra.APIError है, जिसमें Status, Code, Message, RequestID, Field, RetryAfter, Body और Attempts हैं। इसे sentinels के विरुद्ध errors.Is से, या Is* helpers से पहचानें। बाकी statuses भी बिना sentinel वाला *APIError हैं, और non-JSON error body status से map होती है। Cancel या expire हुआ context उसी context की अपनी error लौटाता है, इसलिए errors.Is(err, context.Canceled) काम करता है।
| HTTP status | Class | टिप्पणी |
|---|---|---|
| 400 / local validation | ErrValidation / IsValidation | गलत इनपुट। Validation helpers इसे request भेजने से पहले local रूप से भी उठाते हैं। |
| 401 | ErrAuthentication / IsAuthentication | API key न मिली, गलत है या रद्द हो चुकी है। |
| 402 | ErrPaymentRequired / IsPaymentRequired | वॉलेट में बैलेंस या क्रेडिट कम है। |
| 403 | ErrPermission / IsPermission | सर्वर का संदेश जस का तस दिया जाता है, जैसे Passport और Driving Licence के लिए IP-allowlisted key चाहिए। |
| 404 | ErrNotFound / IsNotFound | ऑर्डर, सेवा या इवेंट की id मौजूद नहीं है। |
| 409 | ErrConflict / IsConflict | Idempotency-Key किसी दूसरी body के साथ दोबारा इस्तेमाल हुई, या state में टकराव है। |
| 429 | ErrRateLimit / IsRateLimit | रुककर दोबारा कोशिश करें; सर्वर Retry-After भेजे तो वह मान उपलब्ध रहता है। |
| 5xx | ErrUpstream / IsUpstream | सर्वर या upstream की ओर की विफलता। 502, 503 और 504 पर अपने आप retry होता है। |
| network / timeout | *ConnectionError (ErrConnection, ErrTimeout) / IsConnection, IsTimeout | कोई उपयोगी response नहीं मिला: कनेक्शन विफल, timeout या cancel। |
| webhook verification | *SignatureVerificationError | गलत या पुराना webhook signature, खाली secret, या गलत timestamp। डिलीवरी को 400 से अस्वीकार करें। |
_, err := client.Verify.GSTIN(ctx, &stampmitra.GSTINVerifyParams{GSTIN: "27ABCDE1234F1Z5"})
var apiErr *stampmitra.APIError
switch {
case err == nil:
// success
case errors.As(err, &apiErr) && stampmitra.IsRateLimit(err):
log.Println("rate limited; retry after", apiErr.RetryAfter) // zero when the API sent no Retry-After
case errors.As(err, &apiErr):
log.Printf("%d %s (request %s): %s", apiErr.Status, apiErr.Code, apiErr.RequestID, apiErr.Message)
case stampmitra.IsValidation(err):
log.Println("invalid input, nothing was sent:", err)
default:
log.Println("request failed:", err)
}Retries और idempotency
- डिफ़ॉल्ट WithMaxRetries(2), exponential backoff और full jitter के साथ (500 ms base, 8 s cap)। Retry-After header (सेकंड या HTTP तारीख़, अधिकतम 60 s) गणना की गई देरी को बदल देता है। timeout (डिफ़ॉल्ट 30 s) हर attempt पर लागू होता है।
- Retry होते हैं: 429, 502, 503, 504, connection errors और timeouts। Retry नहीं होते: बाकी statuses (400, 401, 402, 403, 404, 409, 500 आदि)। GET हमेशा retry के लिए सुरक्षित है।
- SDK webhook replay को छोड़कर हर POST के लिए random UUIDv4 Idempotency-Key बनाता है (crypto/rand से; कमज़ोर generator पर लौटने के बजाय विफल हो जाता है) और अपने आप होने वाले retries में वही key दोहराता है। इसलिए retry हुआ verification, order या payment intent दोबारा शुल्क या दोबारा निर्माण के बजाय पहला नतीजा लौटाता है। अपनी key WithIdempotencyKey से दें।
- बिना key वाला POST (webhook replay) तब तक retry नहीं होता जब तक आप key न दें। जब आपका प्रोग्राम खुद दोबारा चल सकता हो तब अपने order id से बनी key इस्तेमाल करें।
क्लाइंट-साइड validation
भेजने से पहले इनपुट जाँचे जाते हैं (PAN, GSTIN, IFSC, CIN, Udyam, vehicle number, 9 से 18 अंकों का account number, passport file number, driving licence, voter ID, phone, ISO तारीखें, लंबाई की सीमाएँ)। मान trim और ASCII upper-case होते हैं (bank account number सिर्फ़ trim होता है), और identifiers के भीतर के spaces या hyphens अस्वीकार होते हैं, चुपचाप हटाए नहीं जाते। विफलता पर *ValidationError लौटता है (यह ErrValidation से मेल खाता है), बिना network call और बिना शुल्क के, और उसका संदेश मान को दोहराता नहीं। Validators exported हैं (ValidatePAN, ValidateGSTIN, ValidateIFSC आदि); सब कुछ WithValidateInput(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 पर नहीं।
Verification मुख्य package में है: ConstructEvent, ConstructEventFromHeaders और VerifySignature। तुलना constant-time में होती है, अभी से 300 s (WithTolerance) से ज़्यादा दूर के timestamps अस्वीकार होते हैं, खाली secret अस्वीकार होता है, और विफलता पर *SignatureVerificationError लौटता है (Reason: invalid_timestamp, timestamp_outside_tolerance, signature_mismatch आदि), जिसमें secret कभी नहीं होता। परीक्षण के लिए SignPayload signature बनाता है।
// body: raw bytes from io.ReadAll(r.Body); do not decode and re-encode
ev, err := stampmitra.ConstructEventFromHeaders(body, r.Header, secret)
if err != nil {
var sigErr *stampmitra.SignatureVerificationError
if errors.As(err, &sigErr) {
log.Println("rejected:", sigErr.Reason)
}
return
}
switch e := ev.(type) {
case *stampmitra.OrderDocumentReadyEvent:
log.Println("document ready for", e.CoreOrderID, e.ID)
case *stampmitra.UnknownEvent:
log.Println("unrecognised event", e.RawType)
}आठों order.* events typed मानों में decode होते हैं (*OrderCreatedEvent, *OrderPaymentConfirmedEvent, *OrderFulfilmentStartedEvent, *OrderFulfilmentCompletedEvent, *OrderFailedEvent, *OrderCancelledEvent, *OrderRefundCompletedEvent, *OrderDocumentReadyEvent), सभी OrderEvent को embed करते हैं (CoreOrderID, CoreEventType, OccurredAt, ID और Extra)। बाकी सब *UnknownEvent है। डिलीवरी retry होती है, इसलिए X-Webhook-Event-Id की event id से duplicates हटाएँ।
Framework के उदाहरण
package main
import (
"errors"
"io"
"log"
"net/http"
"os"
stampmitra "github.com/Bani-Global-Industries-LLP/stampmitra-developer-go"
)
const maxBody = 1 << 20 // 1 MiB
func handler(secret string) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/webhooks/stampmitra", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Raw bytes: the signature covers the body exactly as received.
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxBody))
if err != nil {
http.Error(w, "cannot read body", http.StatusBadRequest)
return
}
ev, err := stampmitra.ConstructEventFromHeaders(body, r.Header, secret)
if err != nil {
var sigErr *stampmitra.SignatureVerificationError
if errors.As(err, &sigErr) {
log.Printf("rejected webhook: %s", sigErr.Reason) // never log the secret or signature
}
http.Error(w, "invalid signature", http.StatusBadRequest)
return
}
switch e := ev.(type) {
case *stampmitra.OrderCreatedEvent:
log.Printf("order created: %s", e.CoreOrderID)
case *stampmitra.OrderPaymentConfirmedEvent:
log.Printf("payment confirmed: %s", e.CoreOrderID)
case *stampmitra.OrderDocumentReadyEvent:
log.Printf("document ready: %s (event %s)", e.CoreOrderID, e.ID)
case *stampmitra.UnknownEvent:
log.Printf("ignoring unrecognised event %q", e.RawType)
}
w.WriteHeader(http.StatusNoContent) // any 2xx acknowledges; a non-2xx is retried
})
return mux
}
func main() {
log.Fatal(http.ListenAndServe(":8080", handler(os.Getenv("WEBHOOK_SECRET"))))
}Webhooks: इवेंट के प्रकार, payload और retry नीति Webhooks पेज पर हैं।
सुरक्षित logging
डिफ़ॉल्ट रूप से कुछ भी log नहीं होता। WithLogger(slog.New(...)) requests, responses और retries को slog.LevelDebug पर log करता है। Authorization और X-API-Key redact रहते हैं, और bodies में identifiers (PAN, GSTIN, account numbers आदि) व व्यक्तिगत डेटा (नाम, जन्मतिथि, पते) masked रहते हैं।
इंटीग्रेट करने के दूसरे तरीके
REST API किसी भी भाषा से चलती है, और बाकी SDKs भी उसी contract पर बने हैं।
API कॉल करने के लिए तैयार हैं?
मुफ़्त sandbox key लें और ऊपर का quickstart लगभग 5 मिनट में चलाएँ।