Skip to main content

Go Examples

Complete Go example client for integrating with the Multihub API using the standard library (crypto/sha512, net/http, encoding/json).
This is a reference implementation, not an official SDK package. It uses only Go standard library modules with no external dependencies required.

Configuration

Store your credentials in environment variables:
export MULTIHUB_APP_ID="1"
export MULTIHUB_SECRET_KEY="your_secret_key"

MultihubClient Package

package multihub

import (
	"bytes"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

const DefaultBaseURL = "https://api.123hub.pro/public/api/multihub/v1"

// Client is the Multihub API client.
type Client struct {
	AppID      string
	SecretKey  string
	BaseURL    string
	HTTPClient *http.Client
}

// Response is the standard API response envelope.
type Response struct {
	Success        bool            `json:"success"`
	Result         json.RawMessage `json:"result,omitempty"`
	Error          *ErrorDetail    `json:"error,omitempty"`
	Next           interface{}     `json:"next"`
	RequestID      string          `json:"request_id"`
	ProcessingTime float64         `json:"processing_time"`
}

// ErrorDetail contains error information when success is false.
type ErrorDetail struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

// PaymentIdentifiers contains identifiers for looking up a payment.
type PaymentIdentifiers struct {
	CID *int `json:"c_id,omitempty"`
	HID *int `json:"h_id,omitempty"`
}

// Amount represents a monetary amount with currency.
type Amount struct {
	Value    float64 `json:"value"`
	Currency string  `json:"currency"`
}

// Payer contains payer information for a deposit.
type Payer struct {
	Email  string  `json:"email,omitempty"`
	Person *Person `json:"person,omitempty"`
}

// Person contains a person's name.
type Person struct {
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
}

// Receiver contains receiver information for a withdrawal.
type Receiver struct {
	BankAccount string  `json:"bank_account,omitempty"`
	IFSCCode    string  `json:"ifsc_code,omitempty"`
	Person      *Person `json:"person,omitempty"`
}

// BalanceEntry represents a single balance entry in the balance response.
type BalanceEntry struct {
	Currency  string `json:"currency"`
	Available string `json:"available"`
}

// BalanceResult is the parsed result from a balance.get call.
type BalanceResult struct {
	Balances []BalanceEntry `json:"balances"`
}

// PaymentStatusResult is the parsed result from a payment.status call.
type PaymentStatusResult struct {
	Payment struct {
		Status struct {
			Status string `json:"status"`
		} `json:"status"`
	} `json:"payment"`
}

// NewClient creates a new Multihub API client.
// If appID or secretKey are empty, they are read from environment variables.
func NewClient(appID, secretKey string) *Client {
	if appID == "" {
		appID = os.Getenv("MULTIHUB_APP_ID")
	}
	if secretKey == "" {
		secretKey = os.Getenv("MULTIHUB_SECRET_KEY")
	}
	return &Client{
		AppID:     appID,
		SecretKey: secretKey,
		BaseURL:   DefaultBaseURL,
		HTTPClient: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// send signs and sends a request body to the API, returning the response.
func (c *Client) send(body interface{}) (*Response, error) {
	bodyBytes, err := json.Marshal(body)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal body: %w", err)
	}

	// Compute SHA-512 hash of body + secret key
	hasher := sha512.New()
	hasher.Write(bodyBytes)
	hasher.Write([]byte(c.SecretKey))
	hash := hex.EncodeToString(hasher.Sum(nil))

	req, err := http.NewRequest("POST", c.BaseURL, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Data-Application-Id", c.AppID)
	req.Header.Set("X-Data-Hash", hash)

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	var result Response
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("failed to parse response: %w", err)
	}

	return &result, nil
}

// Ping performs a gateway health check.
func (c *Client) Ping() (*Response, error) {
	return c.send(map[string]interface{}{
		"method": "gateway.ping",
		"params": map[string]interface{}{},
	})
}

// CreateDeposit creates a deposit (pay-in) payment.
func (c *Client) CreateDeposit(serviceID int, amount float64, currency string, cID int, extra map[string]interface{}) (*Response, error) {
	payment := map[string]interface{}{
		"identifiers": map[string]interface{}{"c_id": cID},
		"amount":      map[string]interface{}{"value": amount, "currency": currency},
	}
	for k, v := range extra {
		payment[k] = v
	}

	return c.send(map[string]interface{}{
		"method":     "payment.in",
		"service_id": serviceID,
		"params": map[string]interface{}{
			"payment": payment,
		},
	})
}

// CreateWithdrawal creates a withdrawal (payout) payment.
func (c *Client) CreateWithdrawal(serviceID int, amount float64, currency string, cID int, receiver Receiver, extra map[string]interface{}) (*Response, error) {
	payment := map[string]interface{}{
		"identifiers": map[string]interface{}{"c_id": cID},
		"amount":      map[string]interface{}{"value": amount, "currency": currency},
		"receiver":    receiver,
	}
	for k, v := range extra {
		payment[k] = v
	}

	return c.send(map[string]interface{}{
		"method":     "payment.out",
		"service_id": serviceID,
		"params": map[string]interface{}{
			"payment": payment,
		},
	})
}

// GetStatus checks payment status by identifiers.
func (c *Client) GetStatus(cID *int, hID *int) (*Response, error) {
	identifiers := map[string]interface{}{}
	if cID != nil {
		identifiers["c_id"] = *cID
	}
	if hID != nil {
		identifiers["h_id"] = *hID
	}

	return c.send(map[string]interface{}{
		"method": "payment.status",
		"params": map[string]interface{}{
			"payment": map[string]interface{}{
				"identifiers": identifiers,
			},
		},
	})
}

// GetBalance retrieves account balances.
func (c *Client) GetBalance() (*Response, error) {
	return c.send(map[string]interface{}{
		"method": "balance.get",
		"params": map[string]interface{}{},
	})
}

// ParseBalanceResult parses the Result field of a successful balance.get response.
func ParseBalanceResult(resp *Response) (*BalanceResult, error) {
	var result BalanceResult
	if err := json.Unmarshal(resp.Result, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ParsePaymentStatusResult parses the Result field of a successful payment.status response.
func ParsePaymentStatusResult(resp *Response) (*PaymentStatusResult, error) {
	var result PaymentStatusResult
	if err := json.Unmarshal(resp.Result, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

Usage Examples

Ping (Health Check)

package main

import (
	"fmt"
	"log"

	"your-project/multihub"
)

func main() {
	client := multihub.NewClient("1", "your_secret_key")

	resp, err := client.Ping()
	if err != nil {
		log.Fatalf("Network error: %v", err)
	}

	if resp.Success {
		fmt.Println("Gateway is up")
	} else {
		fmt.Printf("Error: %s - %s\n", resp.Error.Code, resp.Error.Message)
	}
}

Create a Deposit Payment

resp, err := client.CreateDeposit(
	14701,   // serviceID
	10000,   // amount
	"INR",   // currency
	12345,   // cID (your client-side identifier)
	map[string]interface{}{
		"description": "Order #12345",
		"payer": multihub.Payer{
			Email: "[email protected]",
			Person: &multihub.Person{
				FirstName: "John",
				LastName:  "Doe",
			},
		},
	},
)
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	fmt.Printf("Payment created (request_id=%s)\n", resp.RequestID)
} else {
	fmt.Printf("Error: %s - %s\n", resp.Error.Code, resp.Error.Message)
}

Create a Withdrawal (Payout)

resp, err := client.CreateWithdrawal(
	14701,  // serviceID
	5000,   // amount
	"INR",  // currency
	67890,  // cID
	multihub.Receiver{
		BankAccount: "1234567890",
		IFSCCode:    "SBIN0001234",
		Person: &multihub.Person{
			FirstName: "Jane",
			LastName:  "Doe",
		},
	},
	map[string]interface{}{
		"description": "Payout #67890",
	},
)
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	fmt.Println("Withdrawal created successfully")
} else {
	fmt.Printf("Error: %s - %s\n", resp.Error.Code, resp.Error.Message)
}

Check Payment Status

// Look up by client-side ID
cID := 12345
resp, err := client.GetStatus(&cID, nil)
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	status, err := multihub.ParsePaymentStatusResult(resp)
	if err != nil {
		log.Fatalf("Failed to parse result: %v", err)
	}
	fmt.Printf("Payment status: %s\n", status.Payment.Status.Status)
} else {
	fmt.Printf("Error: %s - %s\n", resp.Error.Code, resp.Error.Message)
}

// Look up by hub-side ID
hID := 999001
resp, err = client.GetStatus(nil, &hID)

Get Balance

resp, err := client.GetBalance()
if err != nil {
	log.Fatalf("Network error: %v", err)
}

if resp.Success {
	balances, err := multihub.ParseBalanceResult(resp)
	if err != nil {
		log.Fatalf("Failed to parse result: %v", err)
	}
	for _, b := range balances.Balances {
		fmt.Printf("%s: %s\n", b.Currency, b.Available)
	}
} else {
	fmt.Printf("Error: %s - %s\n", resp.Error.Code, resp.Error.Message)
}

Error Handling

Successful responses return HTTP 200, errors return HTTP 400. Always use the Success field to determine the outcome:
resp, err := client.CreateDeposit(14701, 10000, "INR", 12345, nil)
if err != nil {
	// Network-level error (DNS, timeout, connection refused, etc.)
	log.Printf("Network error: %v", err)
	return
}

if resp.Success {
	// Operation succeeded
	fmt.Printf("Request ID: %s\n", resp.RequestID)
	fmt.Printf("Processing time: %.3fs\n", resp.ProcessingTime)
} else {
	// Operation failed - check error code
	switch resp.Error.Code {
	case "INVALID_HASH":
		log.Println("Authentication failed - check your secret key")
	case "INVALID_PARAMS":
		log.Printf("Invalid parameters: %s", resp.Error.Message)
	case "SERVICE_UNAVAILABLE":
		log.Println("Service temporarily unavailable - retry later")
	default:
		log.Printf("API error: %s - %s", resp.Error.Code, resp.Error.Message)
	}
}

Complete Example

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"

	"your-project/multihub"
)

func main() {
	appID := os.Getenv("MULTIHUB_APP_ID")
	secretKey := os.Getenv("MULTIHUB_SECRET_KEY")
	if appID == "" {
		appID = "1"
	}
	if secretKey == "" {
		secretKey = "your_secret_key"
	}

	client := multihub.NewClient(appID, secretKey)

	// 1. Ping the gateway
	pingResp, err := client.Ping()
	if err != nil {
		log.Fatalf("Ping failed: %v", err)
	}
	if !pingResp.Success {
		log.Fatalf("Gateway unreachable: %s", pingResp.Error.Message)
	}
	fmt.Println("Gateway is up")

	// 2. Check balance
	balanceResp, err := client.GetBalance()
	if err != nil {
		log.Printf("Balance request failed: %v", err)
	} else if balanceResp.Success {
		balances, _ := multihub.ParseBalanceResult(balanceResp)
		if balances != nil {
			for _, b := range balances.Balances {
				fmt.Printf("  %s: %s\n", b.Currency, b.Available)
			}
		}
	} else {
		log.Printf("Balance check failed: %s", balanceResp.Error.Message)
	}

	// 3. Create a deposit
	orderID := int(time.Now().Unix())
	depositResp, err := client.CreateDeposit(
		14701,
		10000,
		"INR",
		orderID,
		map[string]interface{}{
			"description": fmt.Sprintf("Order #%d", orderID),
			"payer": map[string]interface{}{
				"email": "[email protected]",
				"person": map[string]interface{}{
					"first_name": "John",
					"last_name":  "Doe",
				},
			},
		},
	)
	if err != nil {
		log.Fatalf("Deposit request failed: %v", err)
	}
	if !depositResp.Success {
		log.Fatalf("Deposit failed: %s", depositResp.Error.Message)
	}
	fmt.Printf("Deposit created (request_id=%s)\n", depositResp.RequestID)

	// 4. Poll for status
	for i := 0; i < 5; i++ {
		time.Sleep(3 * time.Second)

		statusResp, err := client.GetStatus(&orderID, nil)
		if err != nil {
			log.Printf("Status request failed: %v", err)
			break
		}
		if !statusResp.Success {
			log.Printf("Status check failed: %s", statusResp.Error.Message)
			break
		}

		parsed, err := multihub.ParsePaymentStatusResult(statusResp)
		if err != nil {
			log.Printf("Failed to parse status: %v", err)
			break
		}

		status := parsed.Payment.Status.Status
		fmt.Printf("Payment status: %s\n", status)
		if status == "completed" || status == "failed" || status == "cancelled" {
			break
		}
	}
}

// Helper to pretty-print JSON for debugging
func prettyJSON(v interface{}) string {
	b, _ := json.MarshalIndent(v, "", "  ")
	return string(b)
}