API Key Management
Store Keys Securely
Never expose API keys in your code:Bad Example
Copy
Ask AI
// ❌ Never do this
const apiKey = 'AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=';
cURL
Copy
Ask AI
# Use environment variable in shell scripts
curl -X GET https://api.platform.dakota.xyz/customers \
-H "X-API-Key: $DAKOTA_API_KEY"
JavaScript
Copy
Ask AI
// ✅ Use environment variables
const apiKey = process.env.DAKOTA_API_KEY;
Python
Copy
Ask AI
import os
api_key = os.getenv('DAKOTA_API_KEY')
Go
Copy
Ask AI
package main
import "os"
func main() {
apiKey := os.Getenv("DAKOTA_API_KEY")
}
Rust
Copy
Ask AI
use std::env;
fn main() {
let api_key = env::var("DAKOTA_API_KEY")
.expect("DAKOTA_API_KEY environment variable not set");
}
Java
Copy
Ask AI
public class DakotaConfig {
public static void main(String[] args) {
String apiKey = System.getenv("DAKOTA_API_KEY");
if (apiKey == null) {
throw new IllegalStateException("DAKOTA_API_KEY environment variable not set");
}
}
}
Environment Variables Setup
Set up your environment variables properly:.env file
Copy
Ask AI
DAKOTA_API_KEY=AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc=
Shell
Copy
Ask AI
export DAKOTA_API_KEY="AHGlPZaxDSMz8Wf1l8VRH4ObdbHiKsWFWnmRyHtiwAc="
Key Rotation
- Rotate API keys regularly (recommended: every 90 days)
- Create new keys before deactivating old ones to avoid downtime
- Have a key rotation process documented and tested
Access Control
- Limit API key access to only necessary team members
- Use separate keys for different services or applications
- Revoke unused or compromised keys immediately
Request Security
Always Use HTTPS
All API requests must use HTTPS. The Dakota Platform API will reject HTTP requests:JavaScript
Copy
Ask AI
// ✅ Correct - HTTPS
const response = await fetch('https://api.platform.dakota.xyz/customers', {
headers: { 'X-API-Key': process.env.DAKOTA_API_KEY }
});
// ❌ Wrong - HTTP will be rejected
const response = await fetch('http://api.platform.dakota.xyz/customers', {
headers: { 'X-API-Key': process.env.DAKOTA_API_KEY }
});
Validate SSL Certificates
Ensure your HTTP client validates SSL certificates:cURL
Copy
Ask AI
# ✅ Certificate validation enabled by default
curl -X GET https://api.platform.dakota.xyz/customers \
-H "X-API-Key: $DAKOTA_API_KEY"
# ❌ Never disable certificate verification
# curl -k https://api.platform.dakota.xyz/customers # DON'T DO THIS
Node.js
Copy
Ask AI
// ✅ Certificate validation enabled by default
const https = require('https');
// ❌ Never disable certificate validation
// process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; // DON'T DO THIS
Python
Copy
Ask AI
import requests
# ✅ Certificate validation enabled by default
response = requests.get(
'https://api.platform.dakota.xyz/customers',
headers={'X-API-Key': api_key}
)
# ❌ Never disable certificate verification
# requests.get(url, verify=False) # DON'T DO THIS
Go
Copy
Ask AI
package main
import (
"crypto/tls"
"net/http"
)
func main() {
// ✅ Certificate validation enabled by default
client := &http.Client{}
// ❌ Never disable certificate verification
// client := &http.Client{
// Transport: &http.Transport{
// TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // DON'T DO THIS
// },
// }
}
Rust
Copy
Ask AI
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// ✅ Certificate validation enabled by default
let client = Client::new();
// ❌ Never disable certificate verification
// let client = Client::builder()
// .danger_accept_invalid_certs(true) // DON'T DO THIS
// .build()?;
Ok(())
}
Java
Copy
Ask AI
import java.net.http.HttpClient;
import javax.net.ssl.SSLContext;
public class SecurityExample {
public static void main(String[] args) {
// ✅ Certificate validation enabled by default
HttpClient client = HttpClient.newHttpClient();
// ❌ Never disable certificate verification
// Don't create custom trust managers that accept all certificates
}
}
Request Logging Security
When logging requests for debugging, never log sensitive headers:cURL
Copy
Ask AI
# When logging cURL commands, use environment variables
echo "curl -X GET https://api.platform.dakota.xyz/customers \\
-H \"X-API-Key: \$DAKOTA_API_KEY\""
# Never log actual API keys
# echo "curl -H \"X-API-Key: AHGlPZ...\"" # DON'T DO THIS
JavaScript
Copy
Ask AI
function logRequest(url, headers, body) {
const safeHeaders = { ...headers };
// Remove sensitive headers from logs
delete safeHeaders['X-API-Key'];
delete safeHeaders['Authorization'];
console.log('API Request:', {
url,
headers: safeHeaders,
body
});
}
Python
Copy
Ask AI
import logging
def log_request(url, headers, body):
safe_headers = headers.copy()
# Remove sensitive headers from logs
safe_headers.pop('X-API-Key', None)
safe_headers.pop('Authorization', None)
logging.info(f'API Request: {url}, Headers: {safe_headers}')
Go
Copy
Ask AI
package main
import (
"log"
"net/http"
)
func logRequest(req *http.Request) {
safeHeaders := make(map[string][]string)
// Copy headers except sensitive ones
for k, v := range req.Header {
if k != "X-Api-Key" && k != "Authorization" {
safeHeaders[k] = v
}
}
log.Printf("API Request: %s %s, Headers: %v", req.Method, req.URL, safeHeaders)
}
Rust
Copy
Ask AI
use std::collections::HashMap;
use log::info;
fn log_request(url: &str, headers: &HashMap<String, String>, body: &str) {
let mut safe_headers = headers.clone();
// Remove sensitive headers from logs
safe_headers.remove("X-API-Key");
safe_headers.remove("Authorization");
info!("API Request: {}, Headers: {:?}", url, safe_headers);
}
Java
Copy
Ask AI
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
public class RequestLogger {
private static final Logger logger = Logger.getLogger(RequestLogger.class.getName());
public static void logRequest(String url, Map<String, String> headers, String body) {
Map<String, String> safeHeaders = new HashMap<>(headers);
// Remove sensitive headers from logs
safeHeaders.remove("X-API-Key");
safeHeaders.remove("Authorization");
logger.info(String.format("API Request: %s, Headers: %s", url, safeHeaders));
}
}
Production Environment
Base URL
Always use the production base URL (Sandbox coming soon):Copy
Ask AI
https://api.platform.dakota.xyz
Network Security
- Use private networks or VPNs when possible
- Monitor network traffic for unusual patterns
Error Handling
Implement secure error handling that doesn’t expose sensitive information:cURL
Copy
Ask AI
#!/bin/bash
# Secure error handling in shell scripts
make_secure_request() {
local endpoint="$1"
local response
response=$(curl -s -w "%{http_code}" \
-H "X-API-Key: $DAKOTA_API_KEY" \
-H "Content-Type: application/json" \
"https://api.platform.dakota.xyz$endpoint")
local http_code="${response: -3}"
local body="${response%???}"
if [[ "$http_code" -ge 400 ]]; then
# Log full error details internally
echo "API Error: $http_code" >&2
# Return generic error to user
echo "API request failed" >&2
return 1
fi
echo "$body"
}
JavaScript
Copy
Ask AI
async function makeSecureRequest(endpoint) {
try {
const response = await fetch(`https://api.platform.dakota.xyz${endpoint}`, {
headers: {
'X-API-Key': process.env.DAKOTA_API_KEY,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
// Log full error details internally
console.error('API Error:', response.status, response.statusText);
// Return generic error to client
throw new Error('API request failed');
}
return await response.json();
} catch (error) {
// Log error details internally
console.error('Request failed:', error.message);
// Don't expose internal error details
throw new Error('Service temporarily unavailable');
}
}
Python
Copy
Ask AI
import requests
import logging
from typing import Dict, Any
def make_secure_request(endpoint: str) -> Dict[Any, Any]:
try:
response = requests.get(
f'https://api.platform.dakota.xyz{endpoint}',
headers={
'X-API-Key': os.getenv('DAKOTA_API_KEY'),
'Content-Type': 'application/json'
},
timeout=30
)
if not response.ok:
# Log full error details internally
logging.error(f'API Error: {response.status_code} {response.reason}')
# Return generic error to client
raise Exception('API request failed')
return response.json()
except requests.exceptions.RequestException as e:
# Log error details internally
logging.error(f'Request failed: {str(e)}')
# Don't expose internal error details
raise Exception('Service temporarily unavailable')
Go
Copy
Ask AI
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
)
func makeSecureRequest(endpoint string) (map[string]interface{}, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.platform.dakota.xyz"+endpoint, nil)
if err != nil {
log.Printf("Request creation failed: %v", err)
return nil, fmt.Errorf("service temporarily unavailable")
}
req.Header.Set("X-API-Key", os.Getenv("DAKOTA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
// Log error details internally
log.Printf("Request failed: %v", err)
// Don't expose internal error details
return nil, fmt.Errorf("service temporarily unavailable")
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
// Log full error details internally
log.Printf("API Error: %d %s", resp.StatusCode, resp.Status)
// Return generic error to client
return nil, fmt.Errorf("API request failed")
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
log.Printf("JSON decode failed: %v", err)
return nil, fmt.Errorf("service temporarily unavailable")
}
return result, nil
}
Rust
Copy
Ask AI
use reqwest::header::{HeaderMap, HeaderValue};
use serde_json::Value;
use std::env;
use log::error;
async fn make_secure_request(endpoint: &str) -> Result<Value, Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
let api_key = env::var("DAKOTA_API_KEY")
.map_err(|_| "API key not configured")?;
headers.insert("X-API-Key", HeaderValue::from_str(&api_key)?);
headers.insert("Content-Type", HeaderValue::from_static("application/json"));
let url = format!("https://api.platform.dakota.xyz{}", endpoint);
match client.get(&url).headers(headers).send().await {
Ok(response) => {
if !response.status().is_success() {
// Log full error details internally
error!("API Error: {} {}", response.status(), response.status().canonical_reason().unwrap_or("Unknown"));
// Return generic error to client
return Err("API request failed".into());
}
match response.json::<Value>().await {
Ok(data) => Ok(data),
Err(e) => {
error!("JSON decode failed: {}", e);
Err("Service temporarily unavailable".into())
}
}
}
Err(e) => {
// Log error details internally
error!("Request failed: {}", e);
// Don't expose internal error details
Err("Service temporarily unavailable".into())
}
}
}
Java
Copy
Ask AI
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.logging.Logger;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SecureApiClient {
private static final Logger logger = Logger.getLogger(SecureApiClient.class.getName());
private final HttpClient client;
private final ObjectMapper mapper;
public SecureApiClient() {
this.client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(30))
.build();
this.mapper = new ObjectMapper();
}
public JsonNode makeSecureRequest(String endpoint) throws Exception {
String apiKey = System.getenv("DAKOTA_API_KEY");
if (apiKey == null) {
throw new IllegalStateException("API key not configured");
}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.platform.dakota.xyz" + endpoint))
.header("X-API-Key", apiKey)
.header("Content-Type", "application/json")
.GET()
.build();
try {
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
// Log full error details internally
logger.severe("API Error: " + response.statusCode());
// Return generic error to client
throw new Exception("API request failed");
}
return mapper.readTree(response.body());
} catch (IOException | InterruptedException e) {
// Log error details internally
logger.severe("Request failed: " + e.getMessage());
// Don't expose internal error details
throw new Exception("Service temporarily unavailable");
}
}
}
Webhook Security
Verify Webhook Signatures
Always verify webhook signatures to ensure they come from Dakota Platform:cURL
Copy
Ask AI
#!/bin/bash
# Webhook signature verification in shell (example for testing)
verify_webhook_signature() {
local payload="$1"
local signature="$2"
local secret="$WEBHOOK_SECRET"
expected_signature=$(echo -n "$payload" | openssl dgst -sha256 -hmac "$secret" -hex | cut -d' ' -f2)
if [[ "$signature" == "$expected_signature" ]]; then
echo "Signature valid"
return 0
else
echo "Invalid signature" >&2
return 1
fi
}
JavaScript
Copy
Ask AI
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature, 'hex'),
Buffer.from(expectedSignature, 'hex')
);
}
// In your webhook handler
app.post('/webhooks/dakota', (req, res) => {
const signature = req.headers['x-dakota-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook safely
processWebhook(req.body);
res.status(200).send('OK');
});
Python
Copy
Ask AI
import hmac
import hashlib
import json
from flask import Flask, request
def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
app = Flask(__name__)
@app.route('/webhooks/dakota', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Dakota-Signature')
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, os.getenv('WEBHOOK_SECRET')):
return 'Invalid signature', 401
# Process webhook safely
webhook_data = request.get_json()
process_webhook(webhook_data)
return 'OK', 200
Go
Copy
Ask AI
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func verifyWebhookSignature(payload, signature, secret string) bool {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(payload))
expectedSignature := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
signature := r.Header.Get("X-Dakota-Signature")
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading body", http.StatusBadRequest)
return
}
if !verifyWebhookSignature(string(body), signature, os.Getenv("WEBHOOK_SECRET")) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process webhook safely
var webhookData map[string]interface{}
if err := json.Unmarshal(body, &webhookData); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
processWebhook(webhookData)
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "OK")
}
Rust
Copy
Ask AI
use hmac::{Hmac, Mac};
use sha2::Sha256;
use hex;
use std::env;
use warp::Filter;
type HmacSha256 = Hmac<Sha256>;
fn verify_webhook_signature(payload: &str, signature: &str, secret: &str) -> bool {
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.expect("HMAC can take key of any size");
mac.update(payload.as_bytes());
let expected_signature = hex::encode(mac.finalize().into_bytes());
// Use constant-time comparison
expected_signature == signature
}
#[tokio::main]
async fn main() {
let webhook_route = warp::path!("webhooks" / "dakota")
.and(warp::post())
.and(warp::header::<String>("x-dakota-signature"))
.and(warp::body::bytes())
.and_then(handle_webhook);
warp::serve(webhook_route)
.run(([127, 0, 0, 1], 3000))
.await;
}
async fn handle_webhook(
signature: String,
body: bytes::Bytes,
) -> Result<impl warp::Reply, warp::Rejection> {
let payload = String::from_utf8_lossy(&body);
let secret = env::var("WEBHOOK_SECRET")
.map_err(|_| warp::reject::custom("Missing webhook secret"))?;
if !verify_webhook_signature(&payload, &signature, &secret) {
return Ok(warp::reply::with_status(
"Invalid signature",
warp::http::StatusCode::UNAUTHORIZED,
));
}
// Process webhook safely
let webhook_data: serde_json::Value = serde_json::from_str(&payload)
.map_err(|_| warp::reject::custom("Invalid JSON"))?;
process_webhook(webhook_data).await;
Ok(warp::reply::with_status("OK", warp::http::StatusCode::OK))
}
Java
Copy
Ask AI
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.util.Arrays;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class WebhookHandler extends HttpServlet {
private boolean verifyWebhookSignature(String payload, String signature, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
mac.init(keySpec);
byte[] expectedBytes = mac.doFinal(payload.getBytes("UTF-8"));
String expectedSignature = bytesToHex(expectedBytes);
// Use constant-time comparison
return MessageDigest.isEqual(
signature.getBytes(),
expectedSignature.getBytes()
);
} catch (Exception e) {
return false;
}
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
try {
String signature = request.getHeader("X-Dakota-Signature");
String payload = request.getReader().lines()
.collect(java.util.stream.Collectors.joining("\n"));
String secret = System.getenv("WEBHOOK_SECRET");
if (!verifyWebhookSignature(payload, signature, secret)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid signature");
return;
}
// Process webhook safely
processWebhook(payload);
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("OK");
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}
}
Webhook Endpoint Security
- Use HTTPS for all webhook endpoints
- Implement request size limits
- Add rate limiting to webhook endpoints
- Validate webhook payload structure
Monitoring and Alerting
Security Monitoring
Set up monitoring for:- Unusual API usage patterns
- Failed authentication attempts
- Requests from unexpected IP addresses
- High error rates that might indicate attacks
Logging Security Events
Log security-relevant events:cURL
Copy
Ask AI
#!/bin/bash
# Security event logging in shell scripts
log_security_event() {
local event="$1"
local endpoint="$2"
local ip_address="$3"
echo "$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ) | SECURITY | $event | endpoint=$endpoint ip=$ip_address source=dakota-shell-client" >> /var/log/dakota-security.log
}
# Example usage
log_security_event "api_key_used" "/customers" "$CLIENT_IP"
JavaScript
Copy
Ask AI
function logSecurityEvent(event, details) {
console.log(JSON.stringify({
timestamp: new Date().toISOString(),
event: event,
details: details,
source: 'dakota-api-client'
}));
}
// Example usage
logSecurityEvent('api_key_used', {
endpoint: '/customers',
ip_address: req.ip,
user_agent: req.get('User-Agent')
});
Python
Copy
Ask AI
import json
import logging
from datetime import datetime, timezone
def log_security_event(event: str, details: dict):
security_log = {
'timestamp': datetime.now(timezone.utc).isoformat(),
'event': event,
'details': details,
'source': 'dakota-api-client'
}
# Use structured logging
logging.info(json.dumps(security_log))
# Example usage
log_security_event('api_key_used', {
'endpoint': '/customers',
'ip_address': request.remote_addr,
'user_agent': request.headers.get('User-Agent')
})
Go
Copy
Ask AI
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type SecurityEvent struct {
Timestamp string `json:"timestamp"`
Event string `json:"event"`
Details map[string]interface{} `json:"details"`
Source string `json:"source"`
}
func logSecurityEvent(event string, details map[string]interface{}) {
securityEvent := SecurityEvent{
Timestamp: time.Now().UTC().Format(time.RFC3339),
Event: event,
Details: details,
Source: "dakota-api-client",
}
eventJSON, err := json.Marshal(securityEvent)
if err != nil {
log.Printf("Error marshaling security event: %v", err)
return
}
log.Println(string(eventJSON))
}
// Example usage
func handleRequest(w http.ResponseWriter, r *http.Request) {
details := map[string]interface{}{
"endpoint": r.URL.Path,
"ip_address": r.RemoteAddr,
"user_agent": r.UserAgent(),
}
logSecurityEvent("api_key_used", details)
}
Rust
Copy
Ask AI
use serde_json::{json, Value};
use chrono::{DateTime, Utc};
use log::info;
fn log_security_event(event: &str, details: Value) {
let security_event = json!({
"timestamp": Utc::now().to_rfc3339(),
"event": event,
"details": details,
"source": "dakota-api-client"
});
info!("{}", security_event.to_string());
}
// Example usage
fn handle_request(req: &HttpRequest) {
let details = json!({
"endpoint": req.uri().path(),
"ip_address": req.connection_info().remote_addr(),
"user_agent": req.headers().get("user-agent")
.and_then(|h| h.to_str().ok())
.unwrap_or("unknown")
});
log_security_event("api_key_used", details);
}
Java
Copy
Ask AI
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.time.Instant;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
public class SecurityLogger {
private static final Logger logger = Logger.getLogger(SecurityLogger.class.getName());
private static final ObjectMapper mapper = new ObjectMapper();
public static void logSecurityEvent(String event, ObjectNode details) {
try {
ObjectNode securityEvent = mapper.createObjectNode();
securityEvent.put("timestamp", Instant.now().toString());
securityEvent.put("event", event);
securityEvent.set("details", details);
securityEvent.put("source", "dakota-api-client");
logger.info(securityEvent.toString());
} catch (Exception e) {
logger.severe("Error logging security event: " + e.getMessage());
}
}
// Example usage
public static void handleRequest(HttpServletRequest request) {
ObjectNode details = mapper.createObjectNode();
details.put("endpoint", request.getRequestURI());
details.put("ip_address", request.getRemoteAddr());
details.put("user_agent", request.getHeader("User-Agent"));
logSecurityEvent("api_key_used", details);
}
}
Alert Setup
Configure alerts for:- Multiple consecutive API authentication failures
- Requests from new or suspicious IP addresses
- Unusual request volume patterns
- Webhook signature validation failures
Data Protection
Sensitive Data Handling
- Never log sensitive customer data
- Use data encryption at rest for stored API responses
- Implement data retention policies
Security Checklist
Before going to production, verify:- API keys stored as environment variables
- No sensitive data in code or logs
- HTTPS used for all requests
- SSL certificate validation enabled
- Webhook signatures verified
- Error handling doesn’t expose sensitive info
- Request/response logging excludes sensitive headers
- Monitoring and alerting configured
- Data retention policies implemented
- Security testing completed
Incident Response
If you suspect a security incident:- Immediately rotate your API keys
- Review logs for suspicious activity
- Contact Dakota Platform support with incident details
- Document the incident and response
- Update security measures to prevent recurrence