Concepts
Idempotence
Envoyez un Idempotency-Key avec un POST : une nouvelle tentative après une connexion perdue reçoit alors la première réponse, au lieu d’inscrire quelqu’un deux fois.
Pourquoi
Une connexion peut tomber après qu’AttendBeacon a inscrit quelqu’un, mais avant que votre code ne l’apprenne. Renvoyé, le même POST émettrait un second billet. Avec un Idempotency-Key, ce n’est pas le cas : la seconde requête reçoit la réponse de la première.
Comment
Générez votre propre clé pour chaque requête (un UUID est idéal) et envoyez-la avec le POST. Conservez-la avec la requête jusqu’à obtenir une réponse, et renvoyez la même clé à chaque nouvelle tentative.
curl -X POST "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/registrations" \
-H "Authorization: Bearer $ATTENDBEACON_KEY" \
-H "Idempotency-Key: 6f1c2a9e-4b7d-4c0f-8e3a-5b9d1c7e0f42" \
-H "Content-Type: application/json" \
-d '{ "ticketTypeId": "b3ac0006-0000-4000-8000-000000000001", "firstName": "Anna", "lastName": "de Vries", "email": "anna.devries@example.com" }'const idempotencyKey = crypto.randomUUID(); // once per registration, kept for its retries
async function register(person) {
for (let attempt = 1; ; attempt++) {
try {
const response = await fetch(`${base}/events/${eventId}/registrations`, {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Idempotency-Key": idempotencyKey, "Content-Type": "application/json" },
body: JSON.stringify(person)
});
if (response.status < 500) return response; // an answer: the first, or the first again
} catch { /* the connection dropped: try again with the same key */ }
if (attempt === 4) throw new Error("AttendBeacon did not answer.");
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000));
}
}var idempotencyKey = Guid.NewGuid().ToString(); // once per registration, kept for its retries
for (var attempt = 1; ; attempt++)
{
using var request = new HttpRequestMessage(HttpMethod.Post, $"events/{eventId}/registrations") { Content = JsonContent.Create(person) };
request.Headers.Add("Idempotency-Key", idempotencyKey);
try
{
using var response = await client.SendAsync(request);
if ((int)response.StatusCode < 500) return await response.Content.ReadFromJsonAsync<JsonElement>();
}
catch (HttpRequestException) { /* try again with the same key */ }
if (attempt == 4) throw new InvalidOperationException("AttendBeacon did not answer.");
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt)));
}idempotency_key = str(uuid.uuid4()) # once per registration, kept for its retries
for attempt in range(1, 5):
try:
response = requests.post(
f"{base}/events/{event_id}/registrations",
headers={"Authorization": f"Bearer {key}", "Idempotency-Key": idempotency_key},
json=person,
timeout=30,
)
if response.status_code < 500:
break # an answer: the first, or the first again
except requests.ConnectionError:
pass # try again with the same key
time.sleep(2 ** attempt)$idempotencyKey = [guid]::NewGuid().ToString() # once per registration, kept for its retries
$headers["Idempotency-Key"] = $idempotencyKey
$body = $person | ConvertTo-Json
foreach ($attempt in 1..4) {
try {
$attendee = Invoke-RestMethod -Method Post -Uri "$base/events/$eventId/registrations" -Headers $headers -ContentType "application/json" -Body $body
break
} catch {
if ($_.Exception.Response -and [int]$_.Exception.Response.StatusCode -lt 500) { throw }
Start-Sleep -Seconds ([math]::Pow(2, $attempt))
}
}Ce qui est conservé, et combien de temps
La première réponse associée à une clé (son statut, Location et son corps) est conservée 24 heures et renvoyée à toute requête portant la même clé, la même méthode, le même chemin et le même corps. Une réponse rejouée porte Idempotent-Replayed: true.
| Situation | Réponse |
|---|---|
| Nouvelle tentative d’une requête qui a reçu une réponse | La première réponse, avec Idempotent-Replayed: true |
| Nouvelle tentative pendant que la première est encore en cours de traitement | 409 idempotencyKeyInUse, avec Retry-After: 1 |
| La même clé avec une autre méthode, un autre chemin ou un autre corps | 422 idempotencyKeyReused : utilisez une nouvelle clé pour une nouvelle requête |
| Une clé de plus de 255 caractères, ou contenant des espaces | 400 invalidIdempotencyKey |
| La première requête a échoué du côté d’AttendBeacon (5xx) | Rien n’est conservé : la nouvelle tentative est traitée comme une nouvelle requête |
Une clé d’idempotence appartient à la clé API qui l’a envoyée : deux intégrations ne peuvent pas entrer en collision. Les réponses conservées peuvent nommer une personne ; quand cette personne supprime ses données, les réponses qui la nomment sont supprimées aussi.
Quels appels
Chaque POST accepte un Idempotency-Key : inscrire, émettre des billets, annuler, enregistrer une arrivée, créer des événements, des sessions, des codes de réduction et des webhooks. PATCH, PUT et DELETE peuvent être répétés sans risque par nature ; PATCH accepte plutôt If-Match, pour qu’une modification faite entre-temps ne soit pas écrasée (Erreurs, preconditionFailed).