aisplice

In Ihre App einbauen

Anfragesignierung

#Wann Sie sie brauchen

Eine App ohne eigenes Backend muss den Schlüssel in der App ausliefern, wo ihn jeder auslesen kann, der ihren Datenverkehr mitschneidet. Aktivieren Sie für diesen Schlüssel die Anfragesignierung (Konsole → API-Schlüssel → Bearbeiten → Anfragesignierung): Dann muss jede Anfrage zusätzlich eine Signatur tragen, die mit einem separaten Signatur-Secret erstellt wird. So ist der sk-Schlüssel allein nutzlos, und eine mitgeschnittene Anfrage lässt sich nicht wiederholen.

#Die drei Header

Senden Sie Authorization wie bisher und fügen Sie drei Header hinzu. Der signierte Text besteht aus Zeitstempel, Nonce, HTTP-Methode und Pfad (ohne Query-String), durch Zeilenumbrüche verbunden; der HMAC-Schlüssel ist das sig_…-Secret, genau wie in der Konsole angezeigt.

headers
X-Timestamp: 1759312345        # unix seconds, within 5 minutes of server time
X-Nonce:     9f2c4e7a1b3d5f60  # 16-128 chars of A-Z a-z 0-9 - _, new for every request
X-Signature: hex(HMAC-SHA256(secret, "1759312345\n9f2c4e7a1b3d5f60\nPOST\n/v1/images/tasks"))

#Anfrage signieren

sign.dart
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';

const apiKey = 'sk-YOUR_KEY';
const signingSecret = 'sig_YOUR_SIGNING_SECRET';
int clockOffset = 0; // server time - device time, from X-Server-Timestamp

Map<String, String> signedHeaders(String method, String path) {
  final ts = (DateTime.now().millisecondsSinceEpoch ~/ 1000 + clockOffset).toString();
  final random = Random.secure();
  final nonce = List.generate(16, (_) => random.nextInt(256).toRadixString(16).padLeft(2, '0')).join();
  final signature = Hmac(sha256, utf8.encode(signingSecret))
      .convert(utf8.encode('$ts\n$nonce\n$method\n$path'))
      .toString();
  return {
    'Authorization': 'Bearer $apiKey',
    'X-Timestamp': ts,
    'X-Nonce': nonce,
    'X-Signature': signature,
  };
}

// headers: signedHeaders('POST', '/v1/images/tasks')
// GET /v1/images/tasks/{id} is signed with its own path, e.g. '/v1/images/tasks/task_123'
sign.mjs
import crypto from "node:crypto";

const ts = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomBytes(16).toString("hex");
const signature = crypto
  .createHmac("sha256", process.env.SIGNING_SECRET)
  .update(`${ts}\n${nonce}\nPOST\n/v1/images/tasks`)
  .digest("hex");

await fetch("https://www.aisplice.store/v1/images/tasks", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk-YOUR_KEY",
    "Content-Type": "application/json",
    "X-Timestamp": ts,
    "X-Nonce": nonce,
    "X-Signature": signature,
  },
  body: JSON.stringify(payload),
});

#Fehler und Regeln

  • Abgelehnte Anfragen antworten mit 401 und error.code signature_required, signature_expired, signature_invalid oder signature_replayed.
  • signature_expired liefert außerdem X-Server-Timestamp. Handys mit falscher Uhr sollten die Differenz speichern, sie zu jedem Zeitstempel addieren und es einmal mit einer neuen Nonce erneut versuchen.
  • Wiederholungen brauchen eine neue Nonce und eine neue Signatur; eine Nonce wird 10 Minuten lang gespeichert.
  • Wenn Sie das Secret in der Konsole ersetzen, ist das alte innerhalb von 30 Sekunden ungültig. Wird die Signierung aus- und wieder eingeschaltet, bleibt das Secret gleich.
  • Signierung schützt vor mitgeschnittenem Datenverkehr, nicht vor jemandem, der die App entpackt. Verschleiern Sie den Build (Flutter: --obfuscate) und behalten Sie ein Kontingentlimit für den Schlüssel bei.

Immer noch nicht gelöst?

Senden Sie uns die Anfrage-ID oder die Fehlermeldung, und wir sehen uns das an.