Documentation Index
Fetch the complete documentation index at: https://docs.vultlocal.com/llms.txt
Use this file to discover all available pages before exploring further.
Gateway Architecture
This document covers the internal architecture of the Gateway service, including the middleware stack, handler organization, and request lifecycle.
Component Overview
Middleware Stack
Middleware is applied in order, each adding functionality:
| Order | Middleware | Purpose |
|---|
| 1 | CORS | Cross-origin request handling |
| 2 | Request Logger | Log incoming requests |
| 3 | Recovery | Panic recovery with logging |
| 4 | Rate Limiter | Per-client request throttling |
| 5 | Auth | Authentication verification |
| 6 | Audit Logger | Operation audit trail |
Middleware Files
internal/middleware/
├── auth.go # JWT validation
├── api_key_auth.go # API key authentication
├── service_auth.go # Internal service HMAC auth
├── hmac_auth.go # POS HMAC authentication
├── partner_api_auth.go # Partner API authentication
├── rate_limit.go # Request rate limiting
├── cors.go # CORS configuration
├── logging.go # Request/response logging
├── recovery.go # Panic recovery
├── audit.go # Audit logging
├── roles.go # Role-based authorization
└── combined_auth.go # AllowServiceOrUser helper
Handler Organization
Handlers are organized by domain, each handling a specific resource type:
Handler Files
| File | Endpoints | Count |
|---|
subscriber_handler.go | Register, lookup, update, block | 6 |
card_handler.go | Link, block, unblock, upload | 6 |
wallet_handler.go | Balance, payments, transfers | 5 |
agent_handler.go | Lookup, cashin, transfer | 5 |
pos_handler.go | Payment, verify-card | 3 |
compliance_handler.go | Check, alerts, rules | 8 |
admin_handler.go | Login, users, settings | 10+ |
api_key_handler.go | Create, list, revoke keys | 4 |
audit_handler.go | List, query audit logs | 3 |
processor_handler.go | Merchant/processor ops | 6 |
webhook_handler.go | VULT cashin webhook | 2 |
pep_access_handler.go | PEP access flows | 2 |
fee_settings_handler.go | Fee configuration | 4 |
notifications.go | Push notifications | 2 |
suspicious_transactions_handler.go | Fraud monitoring | 4 |
Request Lifecycle
Standard Request Flow
Authentication Flow
gRPC Client
The wallet client wraps gRPC calls to wallet-core:
// internal/wallet/client.go
type Client struct {
conn *grpc.ClientConn
subscriber olive.SubscriberServiceClient
card olive.CardServiceClient
wallet wallet.WalletServiceClient
agent olive.AgentServiceClient
compliance olive.ComplianceServiceClient
// ... other service clients
}
func (c *Client) GetBalance(ctx context.Context, userID, currency string) (*Balance, error) {
resp, err := c.wallet.GetBalance(ctx, &wallet.GetBalanceRequest{
UserId: userID,
Currency: currency,
})
if err != nil {
return nil, mapGRPCError(err)
}
return &Balance{
UserID: resp.UserId,
Currency: resp.Currency,
Balance: resp.Balance,
}, nil
}
Route Registration
Routes are registered in cmd/server/main.go:
func setupRoutes(r *gin.Engine, h *handler.Handler, mw *middleware.Middleware) {
// Public routes
r.GET("/health", h.HealthCheck)
r.GET("/version", h.Version)
// Public subscriber registration
public := r.Group("/api/v1/public")
{
public.POST("/subscribers", h.RegisterSubscriberPublic)
}
// Protected routes
api := r.Group("/api/v1")
api.Use(mw.Auth())
{
// Subscribers
api.POST("/subscribers", h.RegisterSubscriber)
api.GET("/subscribers/lookup", h.LookupSubscriber)
api.GET("/subscribers/:id", h.GetSubscriber)
// Wallet
api.GET("/balance/:user_id", h.GetBalance)
api.POST("/payments", h.CreatePayment)
api.GET("/transactions", h.ListTransactions)
// ... more routes
}
// Admin routes
admin := r.Group("/api/v1/admin")
admin.Use(mw.JWT(), mw.RequireRole("system_admin"))
{
admin.GET("/users", h.ListAdminUsers)
admin.POST("/api-keys", h.CreateAPIKey)
}
// POS routes with HMAC auth
pos := r.Group("/pos")
pos.Use(mw.HMACAuth())
{
pos.POST("/payment", h.POSPayment)
pos.POST("/verify-card", h.POSVerifyCard)
}
}
Error Handling
Standard Error Response
type ErrorResponse struct {
Error string `json:"error"`
Code string `json:"code,omitempty"`
Details interface{} `json:"details,omitempty"`
}
func respondError(c *gin.Context, status int, err error) {
var code string
switch {
case errors.Is(err, ErrNotFound):
code = "NOT_FOUND"
case errors.Is(err, ErrInsufficientBalance):
code = "INSUFFICIENT_BALANCE"
case errors.Is(err, ErrUnauthorized):
code = "UNAUTHORIZED"
default:
code = "INTERNAL_ERROR"
}
c.JSON(status, ErrorResponse{
Error: err.Error(),
Code: code,
})
}
gRPC Error Mapping
func mapGRPCError(err error) error {
st, ok := status.FromError(err)
if !ok {
return err
}
switch st.Code() {
case codes.NotFound:
return ErrNotFound
case codes.InvalidArgument:
return ErrBadRequest
case codes.PermissionDenied:
return ErrForbidden
case codes.ResourceExhausted:
return ErrRateLimited
default:
return ErrInternal
}
}
Configuration Loading
// internal/config/config.go
type Config struct {
Server ServerConfig `yaml:"server"`
Database DatabaseConfig `yaml:"database"`
WalletCore WalletCoreConfig `yaml:"wallet_core"`
Auth AuthConfig `yaml:"auth"`
RateLimit RateLimitConfig `yaml:"rate_limit"`
Logging LoggingConfig `yaml:"logging"`
}
func Load(path string) (*Config, error) {
cfg := &Config{}
// Load YAML file
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
// Expand environment variables
expanded := os.ExpandEnv(string(data))
if err := yaml.Unmarshal([]byte(expanded), cfg); err != nil {
return nil, err
}
// Apply overrides from environment
applyEnvOverrides(cfg)
return cfg, nil
}
Next Steps
Authentication
Detailed auth configuration
Configuration
config.yaml reference