HubSpot drosselt jede App, die zu schnell ruft, mit HTTP 429. Wer darauf mit einer Endlosschleife reagiert, macht es schlimmer. Dieser Artikel erklärt die Limits, zeigt exponential backoff mit Jitter, den Retry-After-Header, Batch-Endpoints und warum Idempotenz dabei Pflicht ist.
Was HTTP 429 wirklich bedeutet
Ein HTTP 429 (Too Many Requests) ist keine Fehlfunktion, sondern eine Ansage. HubSpot teilt dir mit, dass du in einem Zeitfenster mehr Calls geschickt hast, als dein Tarif erlaubt. Der Server hat den Request bewusst abgewiesen, um sich und alle anderen Mandanten zu schützen.
Der falsche Reflex ist, sofort und unverändert erneut zu senden. Damit verschärfst du das Problem, weil dein nächster Call ebenfalls im überfüllten Fenster landet. Der richtige Umgang besteht aus drei Bausteinen: das Limit kennen, beim Treffer kontrolliert zurückweichen und die Anzahl der Calls von vornherein klein halten.
HubSpot hilft dir dabei aktiv. Jede Antwort trägt Header, die dir sagen, wie viel von deinem Budget noch übrig ist und, im Drosselungsfall, wie lange du warten sollst. Wer diese Header liest, muss nicht raten.
Die Limits verstehen statt raten
HubSpot arbeitet mit mehreren überlagerten Limits. Es gibt ein Burst-Limit pro Sekunde und ein Tageslimit. Beide hängen vom Tarif und vom Authentifizierungstyp ab. Für Private Apps gilt ein Limit pro App, für OAuth-Apps wird pro verbundenem Account gezählt.
Statt diese Zahlen fest in den Code zu schreiben, liest du sie zur Laufzeit aus den Headern:
- X-HubSpot-RateLimit-Max nennt das Maximum im aktuellen Fenster.
- X-HubSpot-RateLimit-Remaining nennt den Rest.
- X-HubSpot-RateLimit-Interval-Milliseconds nennt die Fensterlänge.
So kannst du proaktiv drosseln, also bremsen, bevor du überhaupt einen 429 kassierst. Sinkt Remaining gegen null, legst du eine kurze Pause ein. Das ist eleganter, als sich auf den Fehler zu verlassen, und schont die Latenz deiner gesamten HubSpot-Integration.
Exponential backoff mit Jitter
Wenn ein 429 doch kommt, brauchst du eine Rückzugsstrategie, die mit jeder Wiederholung länger wartet. Das Muster heißt exponential backoff: erste Wiederholung nach circa einer Sekunde, dann zwei, dann vier, dann acht, gedeckelt bei einem Maximum.
Reines Verdoppeln hat aber einen Haken. Wenn zehn Worker gleichzeitig einen 429 bekommen, warten alle exakt gleich lang und schlagen danach wieder gleichzeitig zu. Das nennt sich Thundering Herd. Die Lösung ist Jitter: du addierst eine kleine Zufallskomponente auf die Wartezeit, sodass sich die Wiederholungen zeitlich auffächern.
In Python sieht der Kern so aus: wait = min(max_wait, base * 2 ** attempt) + random.uniform(0, jitter), dann time.sleep(wait) und erneut versuchen, bis eine Obergrenze an Versuchen erreicht ist. Wer mit httpx oder requests arbeitet, kann das in eine kleine Retry-Hülle packen, die jeden Call umschließt.
Den Retry-After-Header respektieren
Manchmal sagt dir HubSpot sogar konkret, wie lange du warten sollst. Dann liegt der Header Retry-After in der 429-Antwort, mit einem Wert in Sekunden. Dieser Wert hat Vorrang vor deiner eigenen Backoff-Rechnung.
Die Logik ist klar: Ist Retry-After gesetzt, warte genau so lange (plus etwas Jitter), bevor du es erneut versuchst. Ist er nicht gesetzt, greift dein exponential backoff. So kombinierst du die Anweisung des Servers mit einer robusten Fallback-Strategie.
Ein häufiger Fehler ist, Retry-After zu ignorieren und stattdessen mit einer festen Sekunde weiterzumachen. Damit überholst du die Vorgabe und riskierst, dass HubSpot dich härter drosselt oder vorübergehend sperrt. Lies den Header, halte dich daran, das ist der ganze Trick.
Batch-Endpoints senken die Last an der Wurzel
Der beste Weg, Rate-Limits nicht zu reißen, ist, weniger Calls zu machen. HubSpot bietet dafür Batch-Endpoints unter /crm/v3/objects/contacts/batch/create, /batch/update und /batch/read. Jeder davon verarbeitet bis zu 100 Objekte in einem einzigen Request.
Statt 100 Kontakte einzeln anzulegen, also 100 Calls gegen dein Limit, schickst du einen Batch-Call. Das senkt deinen Verbrauch um den Faktor 100. Bei großen Synchronisationen ist das der Unterschied zwischen einem ruhigen Lauf und einer durchgehenden 429-Wand.
Achte auf die Antwortstruktur: Batch-Calls können teilweise erfolgreich sein. Du bekommst pro Objekt einen Status zurück und musst die fehlgeschlagenen herausfiltern und gezielt erneut versuchen, statt den ganzen Batch blind zu wiederholen. Eine sauber gebaute API-Pipeline trennt erfolgreiche von fehlgeschlagenen Records und führt nur Letztere in die Retry-Schleife.
Idempotenz: das Sicherheitsnetz beim Wiederholen
Jede Retry-Strategie hat eine unangenehme Eigenschaft: Manchmal kam dein erster Request doch durch, nur die Antwort ging verloren. Wenn du dann erneut sendest, legst du denselben Datensatz zweimal an. Bei Kontakten heißt das Dubletten, bei Deals doppelte Umsätze in der Auswertung.
Die Gegenmaßnahme ist Idempotenz: derselbe Aufruf, mehrfach ausgeführt, hat denselben Effekt wie ein einziger. Bei HubSpot erreichst du das über eindeutige Properties. Nutzt du die E-Mail als idempotenten Schlüssel und die upsert-Variante, aktualisiert HubSpot einen bestehenden Kontakt, statt einen neuen anzulegen.
Damit wird Wiederholen ungefährlich, und genau das ist die Voraussetzung für robuste Retries. Wer Backoff ohne Idempotenz baut, tauscht Drosselungsfehler gegen Datenmüll. Beides zusammen ergibt eine Anbindung, die auch unter Last sauber bleibt. So eine HubSpot-Anbindung baue ich als Festpreis-Modul, Eigentum bleibt bei dir. Konditionen findest du unter Preise.