Authentication
Authentication
Getting Started
You should have received an API key from SafeSky. This single key consists of two components:
- API Key ID: Your public identifier
- API Secret: Your private secret key used for signing requests
Keep your API secret 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.
RequestAPI SignatureKey ComponentsDerivation
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:
X-SafeSky-Key-Id:Authorization: SS-HMAC Credential=<your-api-key-idkid>/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=<base64-signature>
X-SafeSky-Timestamp:SS-Date: <unix-timestamp-in-secondsISO8601-timestamp>
X-SafeSky-Signature:SS-Nonce: <hmac-signatureuuid-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
SignatureCanonicalBaseRequest StringConcatenateBuild thefollowingcanonicalcomponentsstring with newlinecharacters (\n):separators:<HTTP-METHOD>\n <REQUEST-PATH>\n <TIMESTAMPQUERY-STRING>\n host:<REQUEST-BODYhost-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, etc.)DELETE)REQUEST-PATH:TheURI pathand query stringonly (e.g.,/)api/v1/flights?status=activeuav:TIMESTAMPQUERY-STRINGUnixQuerytimestampparametersinsortedsecondsalphabetically (must match thee.g.,X-SafeSky-Timestamplat=50.6970&lng=4.3908header)REQUEST-BODY: Raw request body for POST/PUT requests,) 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/
DELETEDELETE)
-
Generate HMAC Signature
Compute the HMAC-SHA256
hash of the signature base string using your API secret:signature:signaturesignature_bytes = HMAC-SHA256(api_secret,hmac_key,signature_base_string)canonical_request) signature = base64(signature_bytes) -
EncodeBuildtheAuthorizationSignatureHeaderConvertConstruct the Authorization header:Authorization: SS-HMAC Credential=<kid>/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=<signature>to hexadecimal format (lowercase).
Example Implementation
######## cURLPython
##!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('/bin/bash', API_KEY_ID='_').replace('=', '')
def derive_hmac_key(api_key):
"your_api_key_id"""Derive API_SECRET=HMAC key using HKDF-SHA256""your_api_secret""
METHOD=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"
PATH="/api/v1/flights"URL TIMESTAMP=$(date +%s)
BODY=""
## Create signature base string
SIGNATURE_BASE="${METHOD}\n${PATH}\n${TIMESTAMP}\n${BODY}"
## Generate HMAC signature
SIGNATURE=$(echo -n -e "${SIGNATURE_BASE}" | openssl dgst -sha256 -hmac "${API_SECRET}" | cut -d' ' -f2)
## Make request
curl -X GET= "https://api.safesky.app${PATH}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")[:-H3] + "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-SafeSky-Key-Id:SS-Date": ${API_KEY_ID}" \
-Htimestamp,
"X-SafeSky-Timestamp:SS-Nonce": ${TIMESTAMP}" \
-Hnonce,
"X-SafeSky-Signature:SS-Alg": ${SIGNATURE}"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
moreoutsidethan±5 minutesoldclockorskewin the future.tolerance. - Character Encoding: Always use UTF-8 encoding for
theallsignaturestringbase string.operations. - Body Serialization: For POST/PUT requests, use the exact body bytes that will be
sent (typically JSON).sent. Don't modify or pretty-print the JSON after signing. - Query
ParametersString:IncludeMustquery parametersbe in thepathcanonicalcomponentrequest as a separate line (e.g.,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
prefix (not/api/v1/flights?status=activeX-SS-X-SafeSky-). Signature CaseAlgorithm:The signature mustMust belowercaseSS-HMAC-SHA256-V1hexadecimal.in theX-SS-Algheader.
Error Responses
Authentication failures will return HTTP 401 with one of the following error codes:messages such as:
: Required headers are missing or malformedinvalid_signatureMissing or invalid HMAC headersInvalid signature: The signature doesn't match the expected value: The timestamp isinvalid_timestampTimestamp outside acceptable rangetoomoreoldthanor±5toominutesfarfrominserverthe futuretime: Theinvalid_keyReplay attack detected - nonce already usedAPInonce has been used in a previous requestInvalid credential - key: The KID is unknown or the API key is inactiveIDnot foundmissing_headers: Required authentication headers are missing
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
to- Use a test endpoint like
/api/v1v1/uav?lat=50&lng=4 Compare your signature generation with the SDK implementationEnable debug logging to inspect the signature base string- Verify
timestampallsynchronizationfourwithrequiredaredateheaders+%svspresent
servertime - Use a test endpoint like
For additional support, contact SafeSky technical support or refer toto: