Authentication
Getting Started
You should have received a single API key from SafeSky (e.g., ssk_live_... or ssk_test_...).
From this single key, both the credential identifier (KID) and the HMAC signing key are automatically derived using cryptographic functions. You don't need to manage separate components - just store and protect your API key.
Keep your API key secure and never expose it in client-side code or public repositories.
⚠️ Important Notice: The legacy
X-API-Keyheader authentication method is deprecated and will be removed in an upcoming release. All integrations must migrate to HMAC authentication as described below. Please update your implementation as soon as possible to avoid service interruption.
HMAC Authentication
SafeSky API uses HMAC (Hash-based Message Authentication Code) for request authentication. This scheme provides several key benefits:
- Security: Credentials are never sent over the network; only a cryptographic signature is transmitted
- Integrity: Ensures requests haven't been tampered with during transit
- Replay Protection: Timestamps prevent replay attacks
- Stateless: No session management required on the server side
HMAC uses the SHA-256 hashing algorithm to create a unique signature for each request based on the request content and your API secret.
SDKs and Libraries
To simplify integration, SafeSky provides official SDKs for multiple platforms:
| Language | Link |
|---|---|
| Python | Documentation |
| Dart / Flutter | Documentation |
| JavaScript / Node.js | Documentation |
| Java | Documentation |
| Kotlin | Documentation |
| C / C++ | Documentation |
| C# | Documentation |
These SDKs handle HMAC signature generation, request signing, and error handling automatically. We recommend using an SDK whenever possible to reduce implementation complexity and potential security issues.
Manual HMAC Implementation
If an SDK is not available for your platform, you can implement HMAC authentication manually.
API Key Derivation
SafeSky uses a single API key from which both the credential identifier (KID) and the HMAC signing key are derived:
-
Derive KID (Credential Identifier)
kid = base64url(SHA256("kid:" + api_key)[0:16])- Take first 16 bytes of SHA-256 hash
- Encode as base64url (using
-instead of+,_instead of/, no padding=)
-
Derive HMAC Signing Key
hmac_key = HKDF-SHA256(api_key, salt="safesky-hmac-salt-v1", info="auth-v1", len=32)- Uses HKDF (HMAC-based Key Derivation Function) with SHA-256
- Produces a 32-byte signing key
Required Headers
Each authenticated request must include the following headers:
Authorization: SS-HMAC Credential=<kid>/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=<base64-signature>
X-SS-Date: <ISO8601-timestamp>
X-SS-Nonce: <uuid-v4>
X-SS-Alg: SS-HMAC-SHA256-V1
Signature Generation Process
-
Generate Request Metadata
timestamp: ISO8601 format (e.g.,2025-11-12T12:00:00.000Z)nonce: UUID v4 (e.g.,123e4567-e89b-12d3-a456-426614174000)
-
Create the Canonical Request String
Build the canonical string with newline separators:
<HTTP-METHOD>\n <REQUEST-PATH>\n <QUERY-STRING>\n host:<host-header>\n x-ss-date:<timestamp>\n x-ss-nonce:<nonce>\n \n <hex-sha256-of-body>HTTP-METHOD: Uppercase HTTP method (GET, POST, PUT, DELETE)REQUEST-PATH: URI path only (e.g.,/v1/uav)QUERY-STRING: Query parameters sorted alphabetically (e.g.,lat=50.6970&lng=4.3908) or empty stringhost: Host header value (e.g.,api.safesky.app)x-ss-date: ISO8601 timestampx-ss-nonce: UUID v4 nonce- Empty line before body hash
- Body hash: SHA-256 of request body in lowercase hex (empty string hash for GET/DELETE)
-
Generate HMAC Signature
Compute the HMAC-SHA256 signature:
signature_bytes = HMAC-SHA256(hmac_key, canonical_request) signature = base64(signature_bytes) -
Build Authorization Header
Construct the Authorization header:
Authorization: SS-HMAC Credential=<kid>/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=<signature>
Example Implementation
######## Python
import hmac
import hashlib
import base64
import uuid
from datetime import datetime, timezone
def derive_kid(api_key):
"""Derive KID from API key"""
kid_string = f"kid:{api_key}"
hash_bytes = hashlib.sha256(kid_string.encode()).digest()
first_16 = hash_bytes[:16]
kid = base64.b64encode(first_16).decode()
return kid.replace('+', '-').replace('/', '_').replace('=', '')
def derive_hmac_key(api_key):
"""Derive HMAC key using HKDF-SHA256"""
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=b"safesky-hmac-salt-v1",
info=b"auth-v1"
)
return hkdf.derive(api_key.encode())
def build_canonical_request(method, path, query_string, host, timestamp, nonce, body):
"""Build canonical request string"""
# Hash the body
body_hash = hashlib.sha256((body or "").encode()).hexdigest()
# Build canonical request
canonical = (
f"{method.upper()}\n"
f"{path}\n"
f"{query_string or ''}\n"
f"host:{host}\n"
f"x-ss-date:{timestamp}\n"
f"x-ss-nonce:{nonce}\n"
f"\n"
f"{body_hash}"
)
return canonical
def generate_signature(canonical_request, hmac_key):
"""Generate HMAC-SHA256 signature"""
signature = hmac.new(hmac_key, canonical_request.encode(), hashlib.sha256).digest()
return base64.b64encode(signature).decode()
# Example usage
API_KEY = "your_api_key_here"
METHOD = "GET"
URL = "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908"
BODY = ""
# Parse URL
from urllib.parse import urlparse
parsed = urlparse(URL)
host = parsed.netloc
path = parsed.path
query = parsed.query
# Generate metadata
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
nonce = str(uuid.uuid4())
# Derive credentials
kid = derive_kid(API_KEY)
hmac_key = derive_hmac_key(API_KEY)
# Build and sign request
canonical = build_canonical_request(METHOD, path, query, host, timestamp, nonce, BODY)
signature = generate_signature(canonical, hmac_key)
# Headers
headers = {
"Authorization": f"SS-HMAC Credential={kid}/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature={signature}",
"X-SS-Date": timestamp,
"X-SS-Nonce": nonce,
"X-SS-Alg": "SS-HMAC-SHA256-V1"
}
######## Java
See the complete SDK implementation at wiki/hmac/java-sdk/SafeSkyHmacAuth.java
Important Considerations
- Clock Synchronization: Ensure your system clock is synchronized with NTP. The API will reject requests with timestamps outside ±5 minutes clock skew tolerance.
- Character Encoding: Always use UTF-8 encoding for all string operations.
- Body Serialization: For POST/PUT requests, use the exact body bytes that will be sent. Don't modify or pretty-print the JSON after signing.
- Query String: Must be in the canonical request as a separate line (sorted alphabetically if multiple parameters).
- Signature Format: Base64-encoded (not hexadecimal).
- Nonce Uniqueness: Each nonce can only be used once within a 15-minute window to prevent replay attacks.
- Header Names: All custom headers use
X-SS-prefix (notX-SafeSky-). - Algorithm: Must be
SS-HMAC-SHA256-V1in theX-SS-Algheader.
Error Responses
Authentication failures will return HTTP 401 with error messages such as:
Missing or invalid HMAC headers: Required headers are missing or malformedInvalid signature: The signature doesn't match the expected valueTimestamp outside acceptable range: The timestamp is more than ±5 minutes from server timeReplay attack detected - nonce already used: The nonce has been used in a previous requestInvalid credential - key not found: The KID is unknown or the API key is inactive
Testing Your Implementation
To verify your implementation:
-
Key Derivation: Verify your KID and HMAC key derivation matches the SDK
- Test KID generation with a known API key
- Ensure base64url encoding is correct (use
-and_, no padding) - Verify HKDF implementation produces correct 32-byte key
-
Canonical Request: Build a canonical request and compare with SDK
- Verify newline characters are correct (
\n) - Check body hash is lowercase hexadecimal SHA-256
- Ensure empty line before body hash
- Verify newline characters are correct (
-
Signature Generation: Compare your HMAC-SHA256 output
- Use base64 encoding (not hexadecimal)
- Verify the signature in Authorization header format
-
Clock Sync: Ensure your system clock is accurate
- Use
date -u +"%Y-%m-%dT%H:%M:%S.000Z"to check ISO8601 format - Server allows ±5 minutes tolerance
- Use
-
Test Request: Start with a simple GET request
- Use a test endpoint like
/v1/uav?lat=50&lng=4 - Verify all four required headers are present
- Use a test endpoint like
For additional support, contact SafeSky technical support or refer to: