StampMitraDevelopers
आधिकारिक SDK

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

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

go get
go get github.com/Bani-Global-Industries-LLP/[email protected]

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

Quickstart

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

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

Go
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)
Authentication

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 में कभी नहीं आती।

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

हर 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 नहीं होती)।

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

Pagination

ListAll खुद कोई I/O नहीं करता; Next पेज lazily लाता है और nextCursor को आगे बढ़ाता है। एक पेज के लिए Logs.List इस्तेमाल करें। Webhooks.Events.List एक *GenericResponse लौटाता है जिसमें Items() और NextCursor() हैं।

Go
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.NextCursor
Errors

Error 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 statusClassटिप्पणी
400 / local validationErrValidation / IsValidationगलत इनपुट। Validation helpers इसे request भेजने से पहले local रूप से भी उठाते हैं।
401ErrAuthentication / IsAuthenticationAPI key न मिली, गलत है या रद्द हो चुकी है।
402ErrPaymentRequired / IsPaymentRequiredवॉलेट में बैलेंस या क्रेडिट कम है।
403ErrPermission / IsPermissionसर्वर का संदेश जस का तस दिया जाता है, जैसे Passport और Driving Licence के लिए IP-allowlisted key चाहिए।
404ErrNotFound / IsNotFoundऑर्डर, सेवा या इवेंट की id मौजूद नहीं है।
409ErrConflict / IsConflictIdempotency-Key किसी दूसरी body के साथ दोबारा इस्तेमाल हुई, या state में टकराव है।
429ErrRateLimit / IsRateLimitरुककर दोबारा कोशिश करें; सर्वर Retry-After भेजे तो वह मान उपलब्ध रहता है।
5xxErrUpstream / IsUpstreamसर्वर या upstream की ओर की विफलता। 502, 503 और 504 पर अपने आप retry होता है।
network / timeout*ConnectionError (ErrConnection, ErrTimeout) / IsConnection, IsTimeoutकोई उपयोगी response नहीं मिला: कनेक्शन विफल, timeout या cancel।
webhook verification*SignatureVerificationErrorगलत या पुराना webhook signature, खाली secret, या गलत timestamp। डिलीवरी को 400 से अस्वीकार करें।
Go
_, 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

Retries और idempotency

Validation

क्लाइंट-साइड 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) से बंद करें।

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 पर नहीं।

Verification मुख्य package में है: ConstructEvent, ConstructEventFromHeaders और VerifySignature। तुलना constant-time में होती है, अभी से 300 s (WithTolerance) से ज़्यादा दूर के timestamps अस्वीकार होते हैं, खाली secret अस्वीकार होता है, और विफलता पर *SignatureVerificationError लौटता है (Reason: invalid_timestamp, timestamp_outside_tolerance, signature_mismatch आदि), जिसमें secret कभी नहीं होता। परीक्षण के लिए SignPayload signature बनाता है।

Go
// 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 के उदाहरण

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

सुरक्षित logging

डिफ़ॉल्ट रूप से कुछ भी log नहीं होता। WithLogger(slog.New(...)) requests, responses और retries को slog.LevelDebug पर log करता है। Authorization और X-API-Key redact रहते हैं, और bodies में identifiers (PAN, GSTIN, account numbers आदि) व व्यक्तिगत डेटा (नाम, जन्मतिथि, पते) masked रहते हैं।

SDKs

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

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

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

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