Skip to main content

HMAC Auth for GO

This SDK provides easy-to-use functions for authenticating requests to the SafeSky API using HMAC signatures.

Click here to download the SDK

Installation

No external dependencies required! Simply copy the SDK file to your project:

# Copy the SDK to your project
cp safesky_hmac_auth.go /path/to/your/project/

Requirements:

    Go 1.18+

    Quick Start

    package main
    
    import (
        "fmt"
        "net/http"
    )
    
    // Import the SDK (copy safesky_hmac_auth.go to your project)
    
    func main() {
        // Your SafeSky API key - REPLACE WITH YOUR ACTUAL API KEY
        apiKey := "YOUR_SAFESKY_API_KEY_HERE"
    
        // Generate authentication headers for a GET request
        headers, err := GenerateAuthHeaders(
            apiKey,
            "GET",
            "https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908",
            "", // Empty body for GET requests
        )
        if err != nil {
            panic(err)
        }
    
        // Use the headers in your HTTP request
        fmt.Println(headers.ToMap())
        // map[Authorization:SS-HMAC Credential=xxx/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=yyy
        //     X-SS-Date:2025-11-12T14:30:00.123Z
        //     X-SS-Nonce:550e8400-e29b-41d4-a716-446655440000
        //     X-SS-Alg:SS-HMAC-SHA256-V1]
    }
    

    API Reference

    GenerateAuthHeaders(apiKey, method, url, body string) (*AuthHeaders, error)

    Generates all required HMAC authentication headers for a SafeSky API request.

    Parameters:

      apiKey (string): Your SafeSky API key (e.g., ssk_live_...) method (string): HTTP method (GET, POST, etc.) url (string): Full request URL including protocol, host, path, and query string body (string): Request body for POST/PUT requests (empty string for GET)

      Returns: *AuthHeaders struct containing all required headers, or error

      Example:

      headers, err := GenerateAuthHeaders(
          "YOUR_SAFESKY_API_KEY_HERE",
          "POST",
          "https://sandbox-public-api.safesky.app/v1/uav",
          `[{"id":"uav1","lat":50.69,"lng":4.39}]`,
      )
      
      DeriveKID(apiKey string) string

      Derives the KID (Key Identifier) from an API key.

      Parameters:

        apiKey (string): Your SafeSky API key

        Returns: string - The derived KID in base64url format

        DeriveHMACKey(apiKey string) []byte

        Derives the HMAC signing key from an API key using HKDF-SHA256.

        Parameters:

          apiKey (string): Your SafeSky API key

          Returns: []byte - The derived HMAC key (32 bytes)

          GenerateNonce() string

          Generates a cryptographically secure nonce (UUID v4 format).

          Returns: string - A UUID v4 nonce

          GenerateTimestamp() string

          Generates the current timestamp in ISO8601 format.

          Returns: string - ISO8601 timestamp (e.g., 2025-11-12T14:30:00.123Z)

          BuildCanonicalRequest(method, path, queryString, host, timestamp, nonce, body string) string

          Builds the canonical request string for HMAC signature.

          Parameters:

            method (string): HTTP method path (string): Request path queryString (string): Query string without leading ? host (string): Host header value timestamp (string): ISO8601 timestamp nonce (string): Unique nonce body (string): Request body

            Returns: string - The canonical request string

            GenerateSignature(canonicalRequest string, hmacKey []byte) string

            Generates HMAC-SHA256 signature for the canonical request.

            Parameters:

              canonicalRequest (string): The canonical request string hmacKey ([]byte): The derived HMAC signing key

              Returns: string - The signature in base64 format

              AuthHeaders Struct

              The AuthHeaders struct contains all authentication headers:

              type AuthHeaders struct {
                  Authorization string  // The full Authorization header
                  XSSDate       string  // X-SS-Date header (timestamp)
                  XSSNonce      string  // X-SS-Nonce header (unique nonce)
                  XSSAlg        string  // X-SS-Alg header (algorithm identifier)
              }
              

              Use ToMap() to convert to a map[string]string for easy use with HTTP clients.

              Complete Examples

              The SDK includes 5 complete examples in example.go:

                GET nearby aircraft - Retrieve traffic in a specific area POST UAV position - Submit UAV telemetry data POST UAV with query parameters - Submit UAV and get nearby traffic POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point Manual step-by-step - Detailed authentication flow

                Run examples with:

                go run example.go safesky_hmac_auth.go
                
                Example 1: GET Request
                package main
                
                import (
                    "fmt"
                    "io"
                    "net/http"
                    "time"
                )
                
                func main() {
                    apiKey := "YOUR_SAFESKY_API_KEY_HERE"
                    url := "https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000"
                
                    // Generate auth headers
                    authHeaders, err := GenerateAuthHeaders(apiKey, "GET", url, "")
                    if err != nil {
                        panic(err)
                    }
                
                    // Create request
                    req, _ := http.NewRequest("GET", url, nil)
                    
                    // Add headers
                    for key, value := range authHeaders.ToMap() {
                        req.Header.Set(key, value)
                    }
                    req.Header.Set("Content-Type", "application/json")
                
                    // Execute
                    client := &http.Client{Timeout: 30 * time.Second}
                    resp, err := client.Do(req)
                    if err != nil {
                        panic(err)
                    }
                    defer resp.Body.Close()
                
                    body, _ := io.ReadAll(resp.Body)
                    fmt.Printf("Status: %d\n", resp.StatusCode)
                    fmt.Printf("Body: %s\n", string(body))
                }
                
                Example 2: POST Request
                package main
                
                import (
                    "bytes"
                    "encoding/json"
                    "fmt"
                    "io"
                    "net/http"
                    "time"
                )
                
                type UAVPosition struct {
                    ID           string  `json:"id"`
                    Latitude     float64 `json:"latitude"`
                    Longitude    float64 `json:"longitude"`
                    Altitude     int     `json:"altitude"`
                    GroundSpeed  int     `json:"ground_speed"`
                    Status       string  `json:"status"`
                    LastUpdate   int64   `json:"last_update"`
                    CallSign     string  `json:"call_sign"`
                    Course       int     `json:"course"`
                    VerticalRate int     `json:"vertical_rate"`
                }
                
                func main() {
                    apiKey := "YOUR_SAFESKY_API_KEY_HERE"
                    url := "https://sandbox-public-api.safesky.app/v1/uav"
                
                    // Create UAV data
                    uavData := []UAVPosition{{
                        ID:           "my_uav_001",
                        Latitude:     50.69378,
                        Longitude:    4.39201,
                        Altitude:     110,
                        GroundSpeed:  10,
                        Status:       "AIRBORNE",
                        LastUpdate:   time.Now().Unix(),
                        CallSign:     "Test UAV",
                        Course:       250,
                        VerticalRate: 5,
                    }}
                
                    bodyBytes, _ := json.Marshal(uavData)
                    bodyStr := string(bodyBytes)
                
                    // Generate auth headers with body
                    authHeaders, err := GenerateAuthHeaders(apiKey, "POST", url, bodyStr)
                    if err != nil {
                        panic(err)
                    }
                
                    // Create request
                    req, _ := http.NewRequest("POST", url, bytes.NewBufferString(bodyStr))
                    
                    // Add headers
                    for key, value := range authHeaders.ToMap() {
                        req.Header.Set(key, value)
                    }
                    req.Header.Set("Content-Type", "application/json")
                
                    // Execute
                    client := &http.Client{Timeout: 30 * time.Second}
                    resp, err := client.Do(req)
                    if err != nil {
                        panic(err)
                    }
                    defer resp.Body.Close()
                
                    respBody, _ := io.ReadAll(resp.Body)
                    fmt.Printf("Status: %d\n", resp.StatusCode)
                    fmt.Printf("Body: %s\n", string(respBody))
                }
                

                Error Handling

                The GenerateAuthHeaders function returns an error if the URL is invalid:

                headers, err := GenerateAuthHeaders(apiKey, "GET", "invalid-url", "")
                if err != nil {
                    log.Fatalf("Failed to generate headers: %v", err)
                }
                

                Thread Safety

                All functions in this SDK are stateless and thread-safe. They can be called concurrently from multiple goroutines.

                Security Notes

                  Keep your API key secure and never commit it to version control Use environment variables or secure configuration for API keys The SDK uses cryptographically secure random number generation for nonces All cryptographic operations use Go's standard library crypto packages