---
title: "Go SDK"
description: "Backend-only Go client for every EPS API — standard library only, context-aware, with HMAC signing built in."
canonical: "https://eps.eko.in/docs/sdk/go"
---


> **Canonical URL:** https://eps.eko.in/docs/sdk/go
> This is a machine-readable Markdown version of the page for AI agents and LLMs. The primary (HTML) version lives at the canonical URL above.

# Go SDK

`github.com/ekoindia/eps-sdk-go` is a standard-library-only client for every EPS
API. One generic `Call(ctx, slug, params)` covers all of them: the endpoint
catalog, each endpoint's params and which of them are required are embedded in
the binary from the same surface these docs are built from, so the client
validates your input **before** it signs and sends anything.

> Backend only. `AccessKey` signs every request — it must never ship in a client
> binary, a mobile app, or anything a user can disassemble.

## Install

```bash
go get github.com/ekoindia/eps-sdk-go
```

Registry: Go modules — https://pkg.go.dev/github.com/ekoindia/eps-sdk-go

| Fact | Value |
| --- | --- |
| Package | `github.com/ekoindia/eps-sdk-go` |
| Requires | Go 1.22 or newer |
| Dependencies | None — `go.mod` has no `require` block and there is no `go.sum`. |
| Source | https://github.com/ekoindia/eps-platform/tree/main/packages/sdk-go |

- Published from a read-only mirror (`ekoindia/eps-sdk-go`) as a git tag; there is no separate registry.


## Your first call

Read the two keys from the environment, pick an environment, and call an
endpoint by its slug:

```go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	eps "github.com/ekoindia/eps-sdk-go"
)

func main() {
	client, err := eps.New(eps.Config{
		DeveloperKey: os.Getenv("EPS_DEVELOPER_KEY"),
		AccessKey:    os.Getenv("EPS_ACCESS_KEY"),
		InitiatorID:  "9962981729",
		Environment:  "sandbox",
	})
	if err != nil {
		log.Fatal(err)
	}

	result, err := client.Call(context.Background(), "pan-lite", map[string]any{
		"pan_number": "ABCDE1234F",
		"name":       "Rajesh Kumar",
		"dob":        "1994-08-29",
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(result)
}
```

`InitiatorID` and `UserCode` are near-constant per developer, so set them once
in `Config`. They are injected into every call as the wire params
`initiator_id` and `user_code` — pass either in `params` to override it for a
single call, or pass `nil` to clear it.

## Client options

| Option | Type | Required | Notes |
| --- | --- | --- | --- |
| `DeveloperKey` | `string` | Yes | Your EPS developer key, sent as the `developer_key` header. |
| `AccessKey` | `string` | Yes | Server-side secret used to sign every request. Never ships to a browser. |
| `Environment` | `"sandbox" \| "production"` | Yes | Selects the base URL from the embedded surface. |
| `InitiatorID` | `string` | No | Default `initiator_id` (registered mobile of the API user) injected into every call. |
| `UserCode` | `string` | No | Default `user_code` (retailer/agent code) injected into every call. |
| `HTTPClient` | `*http.Client` | No | Control timeouts, proxies or retries by supplying your own client. (default: 30s timeout) |

## API surface

The SDK is deliberately small. There is no method per endpoint — `Call()` takes
the slug, and the embedded surface supplies the method, path, and validation
rules. A `*Client` is safe for concurrent use once constructed.

| Member | Kind | Signature | What it does |
| --- | --- | --- | --- |
| `eps.New` | function | `eps.New(cfg eps.Config) (*eps.Client, error)` | Builds a client. Safe for concurrent use once constructed. |
| `Call` | method | `client.Call(ctx context.Context, slug string, params map[string]any) (map[string]any, error)` | Validates, signs and sends one endpoint call. The only SDK with per-call cancellation. |
| `ResolveTarget` | method | `client.ResolveTarget(slug string, params map[string]any) (*eps.Target, error)` | The signed request for a call, without sending it. |
| `BuildHeaders` | method | `client.BuildHeaders(multipartBody bool) map[string]string` | The four auth headers for a single request. |
| `eps.Sign` | function | `eps.Sign(accessKey, timestamp string) string` | The raw signing primitive, exported for debugging. |
| `eps.File` | type | `eps.File{Name string; Content []byte}` | An in-memory upload. |
| `eps.MultipartJSONField` | constant | `MultipartJSONField = "form-data"` | Name of the single form field carrying the JSON envelope on file-upload endpoints. |

## Authentication

You never compute a signature yourself. On every request the client derives
`secret-key = base64(HMAC-SHA256(timestamp, base64(AccessKey)))` and sends it
with `secret-key-timestamp` and `developer_key`. `eps.Sign` is exported only so
you can reproduce a signature while debugging a `403`.

## File uploads

A single `type: "file"` param flips the whole request to `multipart/form-data`.
You still pass every parameter flat; on the wire the SDK packs them the way the
API expects — one form field named `form-data` holding all the non-file params
as a single JSON object, plus one part per upload. A `nil` param is dropped (a
form field has no null encoding), while a `nil` nested inside a map value is
preserved.

File params accept:

- A local file path `string`
- An `eps.File{Name, Content}` (or `*eps.File`) for an in-memory upload

## Errors and timeouts

`Call` returns an error rather than panicking. A non-2xx response comes back as
`*eps.HTTPError` — match it with `errors.As` and read the decoded envelope off
`Body`. Cancel or deadline any call through its `context.Context`; the
`Config.HTTPClient` timeout is the outer bound.

| Type | Raised when | Fields |
| --- | --- | --- |
| `*eps.HTTPError` | Any non-2xx response. Match it with `errors.As`. | `StatusCode`, `URL`, `Body` (decoded envelope or nil), `Raw` |
| `error` | Unknown slug, missing required param, wrong param type, a 2xx body that is not JSON, or a transport failure. Messages are lowercase and `eps:`-prefixed, per Go convention. | — |

| HTTP status | Meaning |
| --- | --- |
| 200 | OK — response returned by our system. |
| 403 | Forbidden — incorrect secret-key or timestamp. |
| 404 | Not Found — wrong request URL. |
| 405 | Method Not Allowed — incorrect HTTP method. |
| 415 | Unsupported Media Type — wrong Content-Type header. |
| 500 | Internal Server Error — connectivity or URL misconfiguration. |


## Environments

Switch with `Config.Environment`; there is no base-URL override.

| Environment | Base URL | Notes |
| --- | --- | --- |
| `sandbox` (UAT / Sandbox) | https://staging.eko.in/ekoapi/v3 | Self-serve credentials available immediately on signup. |
| `production` (Production) | https://api.eko.in/ekoicici/v3 | Credentials issued after organizational KYC. |

## Go-specific notes

- Map keys are sorted before encoding, so query strings and multipart bodies are byte-deterministic.
- The surface is embedded with `go:embed`, so the package needs no network call to resolve a slug.

## Every endpoint

`Call()` accepts every slug in the EPS catalog. Each endpoint's reference page
shows its parameters, response fields and a ready-to-paste Go snippet.

[Browse the API reference](/docs)
