Authentication और Request Signing
अपने project UUID और API key का उपयोग करके HMAC-SHA256 के साथ API request को sign करें।
प्रत्येक API request (incoming webhook को छोड़कर) में आपका project UUID और एक request signature होना ज़रूरी है। Signature यह सिद्ध करता है कि request आपकी ओर से आया है और रास्ते में किसी ने उसे बदला नहीं है।
API keys
2328.io दो keys का उपयोग करता है जो एक ही signing algorithm share करती हैं लेकिन अलग-अलग endpoint को cover करती हैं:
| Key | किस लिए उपयोग होती है |
|---|---|
| API key | भुगतान, स्टैटिक वॉलेट, बैलेंस, विनिमय दर, और payment / static-wallet webhook का verification |
| Payout API key | सभी /v1/payout/* endpoint और payout webhook का verification |
दोनों keys आपके 2328.io project settings में रहती हैं। नीचे के examples में "API key" सामान्य रूप से लिखा है — आप जिस endpoint को call कर रहे हैं, उसके लिए सही key का उपयोग करें।
दोनों keys को कभी न मिलाएँ: सामान्य API key से payout request को sign करना (या payout key से payment request को sign करना) signature error लौटाता है।
आवश्यक headers
| Header | Type | आवश्यक | Description |
|---|---|---|---|
Content-Type | string | हाँ | हमेशा application/json |
project | string | हाँ | आपका project UUID |
sign | string | हाँ | आपकी API key से compute किया गया request का HMAC-SHA256 सिग्नेचर |
User-Agent | string | हाँ | आपके एप्लिकेशन की पहचान करता है (जैसे MyShop/1.4 (+https://myshop.example)). User-Agent के बिना अनुरोध ब्लॉक किए जा सकते हैं। |
Signature कैसे काम करता है
Signature को request body का fingerprint समझें। इसे इस तरह बनाया जाता है:
- Body को JSON में serialize करें (compact — कोई अतिरिक्त whitespace नहीं)।
- उस JSON को Base64 में encode करें। यह कदम input को सभी languages में normalise करता है — एक बार जब यह plain ASCII हो जाता है, हर language HMAC के लिए समान bytes produce करती है।
- अपनी API key का उपयोग करके Base64 string का HMAC-SHA256 compute करें, फिर परिणाम को lowercase hex में बदलें।
GET और बिना body वाले अन्य request types के लिए, JSON के बजाय एक empty string को sign करें।
किसी दिए गए API key के लिए empty-string सिग्नेचर constant होता है। यदि आप कई GET calls करते हैं तो इसे cache कर सकते हैं।
Implementations
<?php
function apiSign(array $data, string $apiKey): string {
$json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$base64 = base64_encode($json);
return hash_hmac('sha256', $base64, $apiKey);
}import crypto from "crypto";
export function apiSign(data, apiKey) {
const json = JSON.stringify(data);
const base64 = Buffer.from(json).toString("base64");
return crypto.createHmac("sha256", apiKey).update(base64).digest("hex");
}import { createHmac } from "crypto";
export function apiSign(data: object, apiKey: string): string {
const json = JSON.stringify(data);
const base64 = Buffer.from(json).toString("base64");
return createHmac("sha256", apiKey).update(base64).digest("hex");
}import json
import hmac
import hashlib
import base64
def api_sign(data: dict, api_key: str) -> str:
# ensure_ascii=False keeps non-ASCII characters (Cyrillic, Chinese, …)
# as-is. Without it, Python escapes them to \uXXXX and the signature
# diverges from PHP / Node / Go.
body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
b64 = base64.b64encode(body.encode("utf-8")).decode()
return hmac.new(api_key.encode(), b64.encode(), hashlib.sha256).hexdigest()package sign
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
)
func ApiSign(data any, apiKey string) (string, error) {
// json.Encoder with SetEscapeHTML(false) — without it, Go escapes <, >, &
// to \u003c etc., which breaks compatibility with PHP / Node / Python.
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(data); err != nil {
return "", err
}
// Encoder appends a trailing newline — drop it.
body := bytes.TrimRight(buf.Bytes(), "\n")
b64 := base64.StdEncoding.EncodeToString(body)
h := hmac.New(sha256.New, []byte(apiKey))
h.Write([]byte(b64))
return hex.EncodeToString(h.Sum(nil)), nil
}बिना body वाले request (GET)
खाली body वाले request के लिए (जैसे GET /v1/payout/status/{uuid}), एक empty string को sign करें:
SIGN=$(printf '' | openssl dgst -sha256 -hmac "$API_KEY" -hex | awk '{print $NF}')$sign = hash_hmac('sha256', base64_encode(''), $apiKey);import { createHmac } from "crypto";
const sign = createHmac("sha256", apiKey)
.update(Buffer.from("").toString("base64"))
.digest("hex");import { createHmac } from "crypto";
const sign: string = createHmac("sha256", apiKey)
.update(Buffer.from("").toString("base64"))
.digest("hex");import hmac
import hashlib
import base64
sign = hmac.new(
api_key.encode(),
base64.b64encode(b"").decode().encode(),
hashlib.sha256,
).hexdigest()package sign
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
)
func EmptyBodySign(apiKey string) string {
b64 := base64.StdEncoding.EncodeToString([]byte(""))
h := hmac.New(sha256.New, []byte(apiKey))
h.Write([]byte(b64))
return hex.EncodeToString(h.Sum(nil))
}पूरा request example
curl -X POST https://api.2328.io/api/v1/payment \
-H "Content-Type: application/json" \
-H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
-H "project: YOUR_PROJECT_UUID" \
-H "sign: YOUR_HMAC_SIGNATURE" \
-d '{"amount":"100.00","currency":"USD","order_id":"ORDER-123"}'<?php
function apiSign(string $body, string $apiKey): string {
return hash_hmac('sha256', base64_encode($body), $apiKey);
}
$project = 'YOUR_PROJECT_UUID';
$apiKey = 'YOUR_API_KEY';
$data = [
'amount' => '100.00',
'currency' => 'USD',
'order_id' => 'ORDER-123',
];
$body = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$sign = apiSign($body, $apiKey);
$ch = curl_init('https://api.2328.io/api/v1/payment');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: MyShop/1.0 (+https://myshop.example)',
"project: $project",
"sign: $sign",
],
]);
$response = json_decode(curl_exec($ch), true);import { createHmac } from "crypto";
function apiSign(body, apiKey) {
const base64 = Buffer.from(body, "utf8").toString("base64");
return createHmac("sha256", apiKey).update(base64).digest("hex");
}
const data = {
amount: "100.00",
currency: "USD",
order_id: "ORDER-123",
};
const body = JSON.stringify(data);
const sign = apiSign(body, process.env.API_KEY);
const res = await fetch("https://api.2328.io/api/v1/payment", {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "MyShop/1.0 (+https://myshop.example)",
project: process.env.PROJECT_UUID,
sign,
},
body,
});
const json = await res.json();import { createHmac } from "crypto";
function apiSign(body: string, apiKey: string): string {
const base64 = Buffer.from(body, "utf8").toString("base64");
return createHmac("sha256", apiKey).update(base64).digest("hex");
}
type CreatePaymentBody = {
amount: string;
currency: string;
order_id: string;
};
type CreatePaymentResponse = { state: number; result: unknown };
const data: CreatePaymentBody = {
amount: "100.00",
currency: "USD",
order_id: "ORDER-123",
};
const body = JSON.stringify(data);
const sign = apiSign(body, process.env.API_KEY!);
const res = await fetch("https://api.2328.io/api/v1/payment", {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "MyShop/1.0 (+https://myshop.example)",
project: process.env.PROJECT_UUID!,
sign,
},
body,
});
const json = (await res.json()) as CreatePaymentResponse;import json
import hmac
import hashlib
import base64
import httpx
def api_sign(body: str, api_key: str) -> str:
b64 = base64.b64encode(body.encode("utf-8")).decode()
return hmac.new(api_key.encode(), b64.encode(), hashlib.sha256).hexdigest()
data = {
"amount": "100.00",
"currency": "USD",
"order_id": "ORDER-123",
}
body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
sign = api_sign(body, API_KEY)
r = httpx.post(
"https://api.2328.io/api/v1/payment",
headers={
"Content-Type": "application/json",
"User-Agent": "MyShop/1.0 (+https://myshop.example)",
"project": PROJECT_UUID,
"sign": sign,
},
content=body.encode("utf-8"),
)
response = r.json()package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"net/http"
)
func ApiSign(body []byte, apiKey string) string {
b64 := base64.StdEncoding.EncodeToString(body)
h := hmac.New(sha256.New, []byte(apiKey))
h.Write([]byte(b64))
return hex.EncodeToString(h.Sum(nil))
}
func marshalCanonical(v any) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
return nil, err
}
return bytes.TrimRight(buf.Bytes(), "\n"), nil
}
func main() {
data := struct {
Amount string `json:"amount"`
Currency string `json:"currency"`
OrderID string `json:"order_id"`
}{
Amount: "100.00",
Currency: "USD",
OrderID: "ORDER-123",
}
body, err := marshalCanonical(data)
if err != nil {
panic(err)
}
sign := ApiSign(body, apiKey)
req, _ := http.NewRequest("POST",
"https://api.2328.io/api/v1/payment",
bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "MyShop/1.0 (+https://myshop.example)")
req.Header.Set("project", projectUUID)
req.Header.Set("sign", sign)
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}अपनी API key को client-side code में कभी expose न करें। Request को अपने backend पर sign करें। एक leak हुई API key किसी को भी आपके merchant account तक पूरी पहुँच दे देती है।
Webhook signatures को verify करना
जब 2328.io आपको webhook भेजता है, तो वही algorithm उल्टा चलता है:
- Payload से
signfield निकालें। - बाकी fields को JSON-encode करें (compact, बिना whitespace)।
- उस string को Base64-encode करें।
- उपयुक्त key के साथ
HMAC-SHA256compute करें। - इसकी तुलना प्राप्त
signसे constant-time comparison के साथ करें (hash_equals,crypto.timingSafeEqual,hmac.compare_digest,subtle.ConstantTimeCompare,OpenSSL.fixed_length_secure_compare)।
Signing key webhook source पर निर्भर करती है:
| Webhook | Verify करने की key |
|---|---|
Payment / static-wallet webhooks (/v1/payment, /v1/static-wallet) | API key |
Payout webhooks (/v1/payout) | Payout API key |
सत्यापन के सामान्य pitfalls. आपका JSON encoder बिल्कुल वही bytes उत्पन्न करना चाहिए जो भेजने वाले ने उत्पन्न किए — अन्यथा Base64 अलग होगा और signature मेल नहीं खाएगी।
- Go:
json.NewEncoderका उपयोगSetEscapeHTML(false)के साथ करें। डिफ़ॉल्टjson.Marshal,<,>,&को<के रूप में escape करता है और signature तोड़ देता है। - Python:
json.dumpsकोensure_ascii=Falseपास करें। इसके बिना, गैर-ASCII (Cyrillic, Chinese, …) अक्षर\uXXXXके रूप में escape हो जाते हैं। - Compact JSON: फ़ील्ड्स के बीच कोई whitespace नहीं (Python में
separators=(",", ":"))। - फ़ील्ड क्रम (Go): एक सादा
map[string]anyre-encode पर keys को randomise कर देता है।json.RawMessage, एक ordered struct का उपयोग करें, या raw bytes सेsignको हटा दें।
यदि सत्यापन बार-बार विफल हो रहा है, तो स्वयं payload पर apiSign चलाएं — यह प्राप्त sign के समान ही hex string उत्पन्न करना चाहिए।
एक वैध signature replays को नहीं रोकती। यह केवल यह साबित करती है कि webhook 2328.io से आया है — यह किसी हमलावर को बाद में कैप्चर किए गए webhook को फिर से पोस्ट करने से नहीं रोकती। फंड क्रेडिट करने से पहले हमेशा uuid (या static wallets के लिए txid) से idempotency जांचें। यदि signature गायब है या गलत है तो HTTP 401 से reject करें।
पूर्ण code examples Webhook Notifications पर हैं। Retry handling और idempotency नियम Best practices में हैं।