OAuth 2.0
Authorization Code Flow mit PKCE für Integrationen in Apps von Drittanbietern.
Rekomi betreibt unter api.rekomi.com/connect/* einen standardkonformen OAuth-2.0-Autorisierungsserver. Bau einen Button „Mit Rekomi verbinden“ in deine App ein, und deine Nutzer geben dir Zugriff auf ihr Rekomi-Konto, ohne einen API-Schlüssel kopieren und einfügen zu müssen.
OAuth 2.0 ist API-Schlüsseln vorzuziehen, wenn:
- du eine Integration baust, die an mehrere Rekomi-Marken verteilt wird (nicht nur an deine eigene Organisation).
- der Nutzer bei der Zustimmung selbst seinen Scope (Berechtigungsumfang) wählen soll (read oder read_write).
- du Refresh-Token-Rotation statt langlebiger statischer Schlüssel brauchst.
- deine Tokens im Rekomi-Dashboard des Nutzers widerrufbar sein müssen.
Integrierst du nur deine eigene Organisation, bleiben API-Schlüssel der einfachere Weg.
Was du bekommst
| Spezifikation | Status |
|---|---|
| Authorization Code Flow + PKCE (S256) | Pflicht |
| Refresh-Tokens mit Rotation + Erkennung von Wiederverwendung | Ja |
| Widerruf von Tokens (RFC 7009) | Ja |
| Token-Introspection (RFC 7662) | Ja |
| Referenz-Tokens (opak, in der Datenbank gespeichert, widerrufbar) | Ja |
| Issuer-Claim in der Autorisierungsantwort (RFC 9207) | Ja |
Öffentliche Clients (reine Browser-Apps) und vertrauliche Clients (serverseitige Apps mit gespeichertem Secret) funktionieren beide. PKCE ist für jeden Client Pflicht, egal welchen Typs.
Endpunkte
| Endpunkt | Methode | Zweck |
|---|---|---|
https://api.rekomi.com/connect/authorize | GET / POST | Startet den Autorisierungsablauf. Leitet den Browser zum Zustimmungsbildschirm weiter. |
https://api.rekomi.com/connect/token | POST | Tauscht den Code gegen Tokens oder rotiert ein Refresh-Token. |
https://api.rekomi.com/connect/revoke | POST | Widerruft ein Access- oder Refresh-Token (RFC 7009). |
https://api.rekomi.com/connect/introspect | POST | Prüft ein Token und liest Scopes, Subject und Ablauf aus (RFC 7662). |
Es gibt nur eine Umgebung (api.rekomi.com); Rekomi hat keinen separaten Issuer für Staging.
Deinen Client registrieren
Um einen Client zu registrieren, schreib an support@rekomi.com mit:
- dem Anzeigenamen deiner App (erscheint auf dem Zustimmungsbildschirm).
- der Logo-URL deiner App (nur https).
- dem Client-Typ:
confidential(der Server hält ein Secret) oderpublic(Browser-App). - den Redirect-URIs: eine oder mehrere https-URLs, zu denen der Nutzer nach der Zustimmung zurückgeschickt wird. Wildcards sind nicht erlaubt; es gilt exakte Übereinstimmung nach RFC 6749, Abschnitt 3.1.2.
- den gewünschten Scopes:
read,read_writeund/oderoffline_access.
Du bekommst eine client_id und (bei vertraulichen Clients) ein client_secret. Das Secret im Klartext wird GENAU EINMAL angezeigt; speichere es sofort. Geht es verloren, bitte den Support um eine Rotation.
Die Registrierung von Clients in Eigenregie steht auf der Roadmap. Bis dahin hält die manuelle Registrierung den Zustimmungsbildschirm frei von minderwertigen oder spammigen „Apps“, denen Nutzer sonst erst misstrauen lernen müssten.
First-Party-Clients
Rekomi pflegt eigene registrierte OAuth-Clients für First-Party-Integrationen. Der erste ist Zapier (der Zustimmungsbildschirm zeigt den App-Namen Zapier), die No-Code-Automatisierung, mit der Marken Rekomi mit über 8,000 anderen Apps verknüpfen. Endnutzer verbinden es genauso wie jede andere OAuth-App eines Drittanbieters: in Zapier auf Connect klicken, zum Zustimmungsbildschirm unter app.rekomi.com/oauth/authorize weitergeleitet werden, eine Organisation wählen, auf Allow klicken. Aufseiten der Marke gibt es außer dem Klick zur Zustimmung nichts zu installieren oder einzurichten.
Bist du ein Endnutzer, der Zapier verbinden will (und kein Entwickler, der einen eigenen OAuth-Client baut), überspring den Rest dieser Seite und lies den Leitfaden zur Zapier-Integration.
Scopes
Aktuell gibt es drei Scopes:
read: Kampagnen, Affiliates, Conversions, Auszahlungen und das Kontoprofil ansehen. Keine Änderungen.read_write: alles ausread, dazu Kampagnen erstellen, Affiliates freigeben, Gutscheine erstellen, Auszahlungen auslösen und Kontoeinstellungen ändern.offline_access: Fordere diesen Scope zusätzlich zureadoderread_writean, um ein Refresh-Token zu bekommen. Ohne ihn enthält die Token-Antwort nur ein Access Token.
Hierarchie: read_write erfüllt jede Anforderung an read. read erfüllt eine Anforderung an read_write NICHT. Fordere den engsten Scope an, den deine Integration wirklich braucht; Nutzer sehen die angeforderten Scopes auf dem Zustimmungsbildschirm, und wir empfehlen, ihnen die Wahl zu lassen, sie weiter einzuschränken.
Nur der Owner einer Organisation kann eine Freigabe mit read_write erteilen; Mitglieder mit niedrigeren Rollen können reinen Lesezugriff freigeben. Versucht jemand ohne Owner-Rolle, read_write zu erteilen, bekommt er 403 insufficient_role.
Der Ablauf
1. Schick den Nutzer zum Autorisierungsendpunkt
GET https://api.rekomi.com/connect/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example.com/oauth/callback
&scope=read_write offline_access
&state=A_RANDOM_STRING_TO_VERIFY_LATER
&code_challenge=BASE64URL_S256_HASH_OF_VERIFIER
&code_challenge_method=S256code_challenge ist der SHA-256-Hash eines zufälligen code_verifier mit 43 bis 128 Zeichen, den du erzeugst, anschließend base64url-kodiert ohne Padding. Bewahre den Verifier für Schritt 3 auf.
state ist dein Schutz gegen CSRF; wir geben den Wert unverändert an deinen Callback zurück, und du musst den Callback ablehnen, wenn er nicht dem gesendeten Wert entspricht.
Der Browser wird über den Zustimmungsbildschirm unter app.rekomi.com/oauth/authorize geleitet. Der Nutzer meldet sich an, falls er es noch nicht ist; der Zustimmungsbildschirm zeigt die Organisation, an die die Freigabe gebunden wird (die gerade aktive), zusammen mit der Identität der App und den angeforderten Scopes. Um eine andere Organisation zu autorisieren, wechselt der Nutzer zuerst im Dashboard die Organisation und startet den Ablauf dann neu. Danach klickt er auf Allow oder Deny.
2. Den Callback verarbeiten
Nach der Zustimmung wird der Nutzer zurückgeleitet zu:
https://yourapp.example.com/oauth/callback
?code=ONE_TIME_AUTHORIZATION_CODE
&state=A_RANDOM_STRING_TO_VERIFY_LATER
&iss=https://api.rekomi.comPrüfe, ob state dem Wert aus Schritt 1 entspricht. Prüfe, ob iss gleich https://api.rekomi.com ist. Schlägt eine der beiden Prüfungen fehl, brich den Ablauf ab.
Hat der Nutzer auf Deny geklickt, bekommst du:
?error=access_denied
&state=A_RANDOM_STRING_TO_VERIFY_LATER3. Den Code gegen Tokens tauschen
Sende innerhalb von 60 Sekunden einen POST an den Token-Endpunkt (so schnell laufen Codes ab):
POST /connect/token HTTP/1.1
Host: api.rekomi.com
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=ONE_TIME_AUTHORIZATION_CODE
&redirect_uri=https://yourapp.example.com/oauth/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&code_verifier=THE_VERIFIER_FROM_STEP_1Antwort:
{
"access_token": "an-opaque-reference-token-256-bits",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "another-opaque-reference-token",
"scope": "read_write"
}access_token ist ein Referenz-Token, KEIN JWT. Es ist opak. Speichere es unverändert und versuch nicht, es zu dekodieren.
4. Die Rekomi-API aufrufen
GET /api/v1/programs HTTP/1.1
Host: api.rekomi.com
Authorization: Bearer ACCESS_TOKEN_FROM_STEP_3Der Bearer authentifiziert UND bestimmt die Organisation, für die der Nutzer zugestimmt hat. Du musst KEINEN separaten Header für die Organisation senden; die Bindung an die Organisation wurde bei der Zustimmung festgelegt und wird bei jeder Anfrage durchgesetzt.
Widerruft der Nutzer deine Autorisierung unter app.rekomi.com/dashboard/settings/connected-apps, liefert der nächste API-Aufruf 401 Unauthorized. Auch darauf folgende Versuche mit dem Refresh-Token liefern 401. Solange noch keine App autorisiert wurde, zeigt diese Seite einen leeren Zustand; sobald sich eine Integration verbindet, erscheint sie dort mit einer Option zum Widerrufen.

5. Tokens erneuern
Access Tokens laufen nach 1 Stunde ab. Bekommst du ein 401, rotiere das Refresh-Token:
POST /connect/token HTTP/1.1
Host: api.rekomi.com
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=YOUR_REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRETDie Antwort hat dieselbe Form wie in Schritt 3. Wichtig: Die Antwort enthält ein neues refresh_token. Das alte wird sofort widerrufen. Wird ein altes Refresh-Token wiederverwendet, wird die gesamte Token-Familie widerrufen (Rekomi folgt hier dem Entwurf OAuth 2.0 Security Best Current Practice). Nach dem Widerruf der Familie muss sich der Nutzer komplett neu verbinden.
Refresh-Tokens gelten 30 Tage. Aktive Integrationen (die innerhalb der 30 Tage erneuern) brauchen praktisch nie eine neue Autorisierung durch den Nutzer.
6. Widerruf
Wird deine Integration deinstalliert, widerrufe alle Tokens, die du hältst:
POST /connect/revoke HTTP/1.1
Host: api.rekomi.com
Content-Type: application/x-www-form-urlencoded
token=ACCESS_OR_REFRESH_TOKEN
&token_type_hint=access_token
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRETEine Antwort 200 bedeutet, dass das Token nicht mehr gültig ist. Nach RFC 7009 bekommst du immer ein 200, wenn deine Client-Zugangsdaten gültig sind; der Antwortinhalt ist leer.
Fehler
| HTTP | OAuth-error | Bedeutung |
|---|---|---|
| 400 | invalid_request | Parameter fehlt oder ist fehlerhaft. |
| 400 | invalid_grant | Code abgelaufen, schon verwendet oder Refresh-Token widerrufen. |
| 401 | invalid_client | client_id oder client_secret falsch. |
| 401 | invalid_token | Access Token widerrufen oder abgelaufen. Erneuern und noch einmal versuchen. |
| 400 | unsupported_grant_type | Aktuell funktionieren nur authorization_code + refresh_token. |
| 400 | unauthorized_client | client_id darf diesen Grant-Typ nicht nutzen. |
| 400 | invalid_scope | Der angeforderte Scope ist für diesen Client nicht registriert. |
Laufzeiten der Tokens
| Token | Standardlaufzeit | Hinweise |
|---|---|---|
| Autorisierungscode | 60 Sekunden | Einmal verwendbar. Innerhalb des Zeitfensters tauschen. |
| Access Token | 1 Stunde | Referenz-Token. Opak. Widerrufbar. |
| Refresh-Token | 30 Tage | Referenz-Token. Rotiert bei jeder Verwendung. |
Die Laufzeiten sind derzeit für jeden Client gleich; wende dich an den Support, wenn deine Compliance-Anforderungen abweichen.
Sicherheitsanforderungen
- PKCE ist Pflicht. Für öffentliche und vertrauliche Clients gleichermaßen. Nur
code_challenge_method=S256. - Exakte Übereinstimmung der redirect_uri. Keine Wildcards, kein Spielraum beim Port, keine Toleranz beim Schema.
- Referenz-Tokens, keine JWTs. Tokens sind opake IDs. Versuch nicht, sie zu dekodieren.
- Rotation der Refresh-Tokens mit Widerruf der Familie. Wird ein schon rotiertes Refresh-Token wiederverwendet, werden alle Tokens der Familie widerrufen.
- Der Widerruf wirkt sofort. Klickt ein Nutzer in seinem Dashboard auf Widerrufen, schlägt der nächste API-Aufruf deiner Integration fehl.
- Keine personenbezogenen Daten in URLs. Tokens erscheinen NIE in Query-Strings. Der Autorisierungsendpunkt nimmt nur die OAuth-Parameter entgegen (die nicht geheim sind).
- Client-Secrets werden gehasht gespeichert. Wir speichern oder protokollieren nie den Klartext.
- Bindung an den Mandanten bei der Zustimmung. Ein Token, das in Organisation A erteilt wurde, kann Organisation B nicht lesen, auch wenn derselbe Nutzer Mitglied in beiden ist.
Ausführliches Beispiel: minimaler Node-Client
import crypto from "node:crypto";
import { URL, URLSearchParams } from "node:url";
const CLIENT_ID = process.env.REKOMI_CLIENT_ID;
const CLIENT_SECRET = process.env.REKOMI_CLIENT_SECRET;
const REDIRECT_URI = "https://yourapp.example.com/oauth/callback";
function base64url(buf) {
return Buffer.from(buf).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
// Step 1: build the authorize URL
export function buildAuthorizeUrl() {
const codeVerifier = base64url(crypto.randomBytes(64));
const codeChallenge = base64url(crypto.createHash("sha256").update(codeVerifier).digest());
const state = base64url(crypto.randomBytes(32));
const url = new URL("https://api.rekomi.com/connect/authorize");
url.searchParams.set("response_type", "code");
url.searchParams.set("client_id", CLIENT_ID);
url.searchParams.set("redirect_uri", REDIRECT_URI);
url.searchParams.set("scope", "read_write offline_access");
url.searchParams.set("state", state);
url.searchParams.set("code_challenge", codeChallenge);
url.searchParams.set("code_challenge_method", "S256");
// Stash codeVerifier + state in the user's session for the callback to read.
return { authorizeUrl: url.toString(), codeVerifier, state };
}
// Step 3: exchange code for tokens
export async function exchangeCode({ code, codeVerifier }) {
const body = new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: REDIRECT_URI,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code_verifier: codeVerifier,
});
const res = await fetch("https://api.rekomi.com/connect/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});
if (!res.ok) throw new Error(`Token exchange failed: ${res.status}`);
return res.json(); // { access_token, refresh_token, expires_in, scope, token_type }
}
// Step 5: refresh
export async function refresh({ refreshToken }) {
const body = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: refreshToken,
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
});
const res = await fetch("https://api.rekomi.com/connect/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});
if (!res.ok) throw new Error(`Refresh failed: ${res.status}`);
return res.json();
}Audit und Nachvollziehbarkeit
Jede erteilte Zustimmung, jede Token-Ausgabe, jede Erneuerung, jeder Widerruf im Dashboard und jede erkannte Wiederverwendung eines Tokens schreibt eine Zeile ins Audit-Log, die Owner der Organisation unter /dashboard/settings/audit-log sehen.
Diese Aktionsnamen begegnen dir:
oauth.consent.granted: Der Nutzer hat auf dem Zustimmungsbildschirm auf Allow geklickt.oauth.consent.revoked: Der Nutzer hat auf seiner Seite Verbundene Apps auf Widerrufen geklickt.oauth.token.issued: Ein Autorisierungscode wurde gegen Tokens getauscht.oauth.token.refreshed: Die Rotation per Refresh war erfolgreich.oauth.token.reuse_detected: Ein bereits rotiertes Refresh-Token wurde wiederverwendet; die gesamte Familie wurde widerrufen.
Vergleich mit API-Schlüsseln
| Fähigkeit | API-Schlüssel | OAuth 2.0 |
|---|---|---|
| Einrichtungszeit | Sekunden | Minuten (Registrierung des Clients) |
| Geeignet für | Deine eigene Organisation | Mandantenfähige Integrationen |
| Form des Tokens | Statischer String rk_live_* | Opakes Referenz-Token |
| Erneuerung | Keine (langlebig) | Ja (Rotation, 30 Tage) |
| Für Nutzer im Dashboard sichtbar | Einstellungen → API-Schlüssel | Einstellungen → Verbundene Apps |
| Nutzer kann selbst widerrufen | Nur der Owner | Ja (jederzeit) |
| Wahl des Scopes | Beim Erstellen | Bei der Zustimmung |
| Zuordnung pro Nutzer | Nein (auf Organisationsebene) | Ja (der Clerk-Nutzer ist das Subject) |
Nimm API-Schlüssel für deine eigene interne Automatisierung. Nimm OAuth 2.0, wenn du an andere Marken verteilst.
Authentifizierung
Bearer-API-Schlüssel, Berechtigungen (Scopes), Signatur-Secrets, Rotation und Widerruf.
Referenz der S2S-Tracking-API
HMAC-signierte Übermittlung von Conversions für jeden Zahlungsanbieter jenseits der direkten Verbindungen zu Stripe, Paddle, Braintree, Shopify, Wix, BigCommerce, Lemon Squeezy, Chargebee, Polar, Recurly, Gumroad, Creem, Dodo Payments und Mollie.