Go SDK Reference
Complete API reference for the Sigil Auth Go SDK
Go SDK Reference
The sigilauth package provides an idiomatic Go client for integrating Sigil Auth into your applications.
[!NOTE] Pre-release: The SDK is not yet published as a standalone Go module. For local development, use a
replacedirective in yourgo.mod:replace github.com/sigilauth/server/sdk-go => /path/to/sigilauth/server/sdk-go
Installation
Once published:
go get github.com/sigilauth/server/sdk-go
Requires Go 1.21 or later.
Quick Example
package main
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/sigilauth/server/sdk-go"
)
func main() {
client, err := sigilauth.New(sigilauth.Config{
ServiceURL: "https://sigil.example.com",
APIKey: os.Getenv("SIGIL_API_KEY"),
HTTPTimeout: 30 * time.Second,
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
challenge, err := client.Auth.CreateChallenge(ctx, &sigilauth.ChallengeRequest{
Fingerprint: "a1b2c3...",
DevicePublicKey: "Ag8xYz...",
Action: sigilauth.Action{
Type: "step_up",
Description: "Transfer $5000 to external account",
},
})
if err != nil {
log.Fatal(err)
}
result, err := client.Auth.AwaitResult(ctx, challenge.ChallengeID, nil)
if err != nil {
log.Fatal(err)
}
fmt.Println("Status:", result.Status)
}
Configuration
sigilauth.New(config)
Creates a new Sigil Auth client.
Type: func New(config Config) (*Client, error)
Config struct:
type Config struct {
ServiceURL string
APIKey string
HTTPTimeout time.Duration
TLSCertPinning []string // Optional PEM-encoded certs
}
| Field | Type | Required | Description |
|---|---|---|---|
ServiceURL |
string |
Yes | HTTPS URL of your Sigil service |
APIKey |
string |
Yes | Load from env (os.Getenv("SIGIL_API_KEY")) |
HTTPTimeout |
time.Duration |
Yes | Request timeout (recommend 30 * time.Second) |
TLSCertPinning |
[]string |
No | Optional PEM-encoded certificates for pinning |
Returns:
*Client— Configured client withAuth,MPA, andWebhooksserviceserror— Config validation error
Example:
client, err := sigilauth.New(sigilauth.Config{
ServiceURL: "https://sigil.example.com",
APIKey: os.Getenv("SIGIL_API_KEY"),
HTTPTimeout: 30 * time.Second,
})
if err != nil {
log.Fatal(err)
}
Security:
The SDK enforces TLS 1.2+ and validates certificates by default. You cannot disable TLS verification.
Expected API key format:
sgk_test_<64 hex characters>
sgk_live_<64 hex characters>
Authentication API
client.Auth.CreateChallenge(ctx, request)
Creates a new authentication challenge and sends a push notification to the device.
Type: func (s *AuthService) CreateChallenge(ctx context.Context, req *ChallengeRequest) (*ChallengeResult, error)
Request struct:
type ChallengeRequest struct {
Fingerprint string `json:"fingerprint"`
DevicePublicKey string `json:"device_public_key"`
Action Action `json:"action,omitempty"`
}
type Action struct {
Type string `json:"type,omitempty"`
Description string `json:"description,omitempty"`
Params map[string]interface{} `json:"params,omitempty"`
}
| Field | Type | Required | Description |
|---|---|---|---|
Fingerprint |
string |
Yes | SHA-256 hash of device public key (64 hex chars) |
DevicePublicKey |
string |
Yes | Device’s P-256 public key (base64, 33 bytes compressed) |
Action |
Action |
No | Context shown to user on approval screen |
Action.Type |
string |
No | Action type (e.g., "step_up", "delete_account") |
Action.Description |
string |
No | Human-readable description |
Action.Params |
map[string]interface{} |
No | Additional structured context |
Returns: *ChallengeResult
type ChallengeResult struct {
ChallengeID string `json:"challenge_id"`
Fingerprint string `json:"fingerprint"`
Pictogram []string `json:"pictogram"`
PictogramSpeakable string `json:"pictogram_speakable"`
ExpiresAt string `json:"expires_at"`
}
| Field | Type | Description |
|---|---|---|
ChallengeID |
string |
UUID for this challenge |
Fingerprint |
string |
Device fingerprint (echoed back) |
Pictogram |
[]string |
Five emoji identifying this challenge |
PictogramSpeakable |
string |
Space-separated words (e.g., "apple banana plane car dog") |
ExpiresAt |
string |
ISO 8601 timestamp when challenge expires (5 minutes) |
Errors:
Returns error for HTTP errors:
- Invalid fingerprint format or mismatch
- Missing/invalid API key
- Device not registered
- Rate limit exceeded
Example:
challenge, err := client.Auth.CreateChallenge(ctx, &sigilauth.ChallengeRequest{
Fingerprint: "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
DevicePublicKey: "Ag8xYzI3ZWRkNDUzYmNlYzVmMTJjNmI5MzA4OGY0ZjIxYWI5NzM4NTJhOWU2ZGUzMg==",
Action: sigilauth.Action{
Type: "step_up",
Description: "Transfer $5000 to external account",
Params: map[string]interface{}{
"recipient": "sarah@example.com",
"amount_usd": 5000,
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Challenge ID:", challenge.ChallengeID)
fmt.Println("Pictogram:", challenge.Pictogram)
fmt.Println("Speakable:", challenge.PictogramSpeakable)
client.Auth.GetStatus(ctx, challengeID)
Checks the current status of a challenge.
Type: func (s *AuthService) GetStatus(ctx context.Context, challengeID string) (*ChallengeStatus, error)
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
challengeID |
string |
Yes | UUID from CreateChallenge |
Returns: *ChallengeStatus
type ChallengeStatus struct {
Status string `json:"status"`
ChallengeID string `json:"challenge_id"`
Fingerprint string `json:"fingerprint"`
CreatedAt string `json:"created_at"`
ExpiresAt string `json:"expires_at"`
RespondedAt string `json:"responded_at,omitempty"`
}
| Field | Type | Description |
|---|---|---|
Status |
string |
"pending", "verified", "rejected", "expired" |
ChallengeID |
string |
UUID |
Fingerprint |
string |
Device fingerprint |
CreatedAt |
string |
ISO 8601 timestamp |
ExpiresAt |
string |
ISO 8601 timestamp |
RespondedAt |
string |
When device responded (empty if pending) |
Example:
status, err := client.Auth.GetStatus(ctx, "550e8400-e29b-41d4-a716-446655440000")
if err != nil {
log.Fatal(err)
}
if status.Status == "verified" {
fmt.Println("User approved at", status.RespondedAt)
}
client.Auth.AwaitResult(ctx, challengeID, options)
Polls for challenge completion. Blocks until the user approves, denies, or the challenge expires.
Type: func (s *AuthService) AwaitResult(ctx context.Context, challengeID string, opts *AwaitOptions) (*ChallengeStatus, error)
Parameters:
type AwaitOptions struct {
PollInterval int // Milliseconds between status checks
MaxAttempts int // Maximum number of poll attempts
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
PollInterval |
int |
No | 1000 |
Milliseconds between checks |
MaxAttempts |
int |
No | 30 |
Maximum poll attempts |
Pass nil for opts to use defaults (30 seconds total: 30 attempts × 1 second).
Returns: *ChallengeStatus
Returns when status becomes verified, rejected, or expired.
Returns ErrMaxAttemptsReached if polling exhausts max attempts before reaching a terminal state.
Respects ctx.Done() for early cancellation.
Example:
result, err := client.Auth.AwaitResult(ctx, challenge.ChallengeID, &sigilauth.AwaitOptions{
PollInterval: 1000, // 1 second
MaxAttempts: 60, // 60 seconds total
})
if err != nil {
if errors.Is(err, sigilauth.ErrMaxAttemptsReached) {
fmt.Println("Polling timeout")
} else {
log.Fatal(err)
}
return
}
switch result.Status {
case "verified":
fmt.Println("User approved!")
case "rejected":
fmt.Println("User denied the request.")
case "expired":
fmt.Println("Challenge expired.")
}
Using context timeout:
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancel()
result, err := client.Auth.AwaitResult(ctx, challenge.ChallengeID, nil)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("Context timeout")
} else {
log.Fatal(err)
}
}
Multi-Party Approval (MPA)
client.MPA.Request(ctx, request)
Creates a multi-party approval request. Challenges are sent to multiple devices. The request is approved when M out of N groups approve.
Type: func (s *MPAService) Request(ctx context.Context, req *MPARequest) (*MPAResult, error)
Request struct:
type MPARequest struct {
Required int `json:"required"`
Groups []ApprovalGroup `json:"groups"`
Action Action `json:"action"`
ExpiresInSeconds int `json:"expires_in_seconds,omitempty"`
}
type ApprovalGroup struct {
Name string `json:"name"`
Approvers []Approver `json:"approvers"`
}
type Approver struct {
Fingerprint string `json:"fingerprint"`
DevicePublicKey string `json:"device_public_key"`
}
| Field | Type | Required | Description |
|---|---|---|---|
Required |
int |
Yes | Number of groups that must approve (M) |
Groups |
[]ApprovalGroup |
Yes | Array of approval groups (N total) |
Action |
Action |
Yes | Action context shown on all devices |
ExpiresInSeconds |
int |
No | Time to approve (default: 300, min: 60, max: 900) |
Returns: *MPAResult
type MPAResult struct {
RequestID string `json:"request_id"`
GroupsRequired int `json:"groups_required"`
GroupsTotal int `json:"groups_total"`
ExpiresAt string `json:"expires_at"`
}
Example:
mpaRequest, err := client.MPA.Request(ctx, &sigilauth.MPARequest{
Required: 2,
Groups: []sigilauth.ApprovalGroup{
{
Name: "Engineering Leads",
Approvers: []sigilauth.Approver{
{
Fingerprint: "abc123...",
DevicePublicKey: "Ag8xYz...",
},
},
},
{
Name: "Security Team",
Approvers: []sigilauth.Approver{
{
Fingerprint: "def456...",
DevicePublicKey: "AhMxNj...",
},
},
},
{
Name: "Operations",
Approvers: []sigilauth.Approver{
{
Fingerprint: "ghi789...",
DevicePublicKey: "AjQyOT...",
},
{
Fingerprint: "jkl012...",
DevicePublicKey: "AkU3MT...",
},
},
},
},
Action: sigilauth.Action{
Type: "cold_boot",
Description: "Cold boot engine ENG-001",
Params: map[string]interface{}{
"engine_id": "ENG-001",
"reason": "Scheduled maintenance",
},
},
ExpiresInSeconds: 600, // 10 minutes
})
if err != nil {
log.Fatal(err)
}
fmt.Println("Request ID:", mpaRequest.RequestID)
client.MPA.GetStatus(ctx, requestID)
Checks the current status of an MPA request.
Type: func (s *MPAService) GetStatus(ctx context.Context, requestID string) (*MPAStatus, error)
Returns: *MPAStatus
type MPAStatus struct {
Status string `json:"status"`
RequestID string `json:"request_id"`
GroupsRequired int `json:"groups_required"`
GroupsSatisfied []string `json:"groups_satisfied,omitempty"`
CreatedAt string `json:"created_at"`
ExpiresAt string `json:"expires_at"`
CompletedAt string `json:"completed_at,omitempty"`
}
| Field | Type | Description |
|---|---|---|
Status |
string |
"pending", "approved", "rejected", "timeout" |
RequestID |
string |
UUID |
GroupsRequired |
int |
M |
GroupsSatisfied |
[]string |
Names of groups that have approved |
CreatedAt |
string |
ISO 8601 timestamp |
ExpiresAt |
string |
ISO 8601 timestamp |
CompletedAt |
string |
When request reached terminal state |
Example:
status, err := client.MPA.GetStatus(ctx, "7c3e5f9a-2b4d-4a1e-8f6c-9d2a1b3c4e5f")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d of %d groups approved\n", len(status.GroupsSatisfied), status.GroupsRequired)
client.MPA.AwaitResult(ctx, requestID, options)
Polls for MPA completion.
Type: func (s *MPAService) AwaitResult(ctx context.Context, requestID string, opts *AwaitOptions) (*MPAStatus, error)
Parameters:
Same AwaitOptions as Auth. Default: PollInterval: 1000ms, MaxAttempts: 30.
Returns: *MPAStatus
Example:
result, err := client.MPA.AwaitResult(ctx, mpaRequest.RequestID, &sigilauth.AwaitOptions{
PollInterval: 3000, // 3 seconds (MPA is slower)
MaxAttempts: 60, // 3 minutes total
})
if err != nil {
log.Fatal(err)
}
if result.Status == "approved" {
fmt.Println("MPA approved by", result.GroupsSatisfied)
// Proceed with the action
} else if result.Status == "rejected" {
fmt.Println("At least one group denied.")
} else {
fmt.Println("Request timed out.")
}
Webhooks
client.Webhooks.Verify(headers, body, secret)
Verifies the HMAC signature on a webhook delivery from Sigil.
Type: func (s *WebhookService) Verify(headers http.Header, body []byte, secret string) error
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
headers |
http.Header |
Yes | HTTP headers from the webhook request |
body |
[]byte |
Yes | Raw request body (not parsed JSON) |
secret |
string |
Yes | Webhook secret from Sigil config |
Returns: error
nilif signature is validErrMissingSignature,ErrMissingTimestamp,ErrInvalidSignatureon failure
Example:
func handleWebhook(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read body", http.StatusBadRequest)
return
}
if err := client.Webhooks.Verify(r.Header, body, os.Getenv("SIGIL_WEBHOOK_SECRET")); err != nil {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
var event WebhookEvent
if err := json.Unmarshal(body, &event); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
log.Printf("Webhook event: %s", event.Type)
w.WriteHeader(http.StatusOK)
}
client.Webhooks.VerifyTimestamp(timestamp, maxAge)
Checks if the webhook timestamp is within acceptable range.
Type: func (s *WebhookService) VerifyTimestamp(timestamp string, maxAge int) error
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
timestamp |
string |
Yes | Value of X-Sigil-Timestamp header (Unix timestamp) |
maxAge |
int |
Yes | Maximum age in seconds (recommend: 300) |
Returns: error
nilif timestamp is validErrInvalidTimestamp,ErrTimestampTooOld,ErrTimestampInFutureon failure
Example:
timestamp := r.Header.Get("X-Sigil-Timestamp")
if err := client.Webhooks.VerifyTimestamp(timestamp, 300); err != nil {
http.Error(w, "Timestamp validation failed", http.StatusUnauthorized)
return
}
Error Handling
All SDK methods return standard Go errors. Check for sentinel errors where appropriate:
import "errors"
result, err := client.Auth.AwaitResult(ctx, challengeID, nil)
if err != nil {
if errors.Is(err, sigilauth.ErrMaxAttemptsReached) {
// Handle polling timeout
} else if errors.Is(err, context.DeadlineExceeded) {
// Handle context timeout
} else {
// Handle other errors
log.Fatal(err)
}
}
Sentinel errors:
sigilauth.ErrMaxAttemptsReached— Polling exhausted max attemptssigilauth.ErrMissingSignature— Webhook missing signature headersigilauth.ErrMissingTimestamp— Webhook missing timestamp headersigilauth.ErrInvalidSignature— Webhook signature verification failedsigilauth.ErrInvalidTimestamp— Webhook timestamp is malformedsigilauth.ErrTimestampTooOld— Webhook timestamp exceeds max agesigilauth.ErrTimestampInFuture— Webhook timestamp is in the future
Next Steps
- Node SDK Reference — TypeScript equivalent of this SDK
- Integrator Quickstart — Quick path to your first auth
- MPA Setup Guide — How to configure multi-party approval policies
- Self-Hosting Guide — Deploy Sigil Auth with Docker