Skip to content

Idempotency

Networks fail. If your request times out you can't tell whether the payment was created, so you retry. An idempotency key makes that retry safe: the same key always returns the same payment.

  • The Idempotency-Key header is required on POST /v1/payments. Requests without one get 400 VALIDATION_ERROR.
  • The SDKs generate a random UUID for you, which protects retries within one process.
  • To stay safe across restarts and deploys, pass your own key tied to your order, such as order_1042.
  • Sending a key that was already used returns the original response again, without creating a second payment. Use a new key for a genuinely new payment.
  • If a request with the same key is still in progress, you get 409 CONFLICT. Wait and retry.
await oura.payments.create(params, { idempotencyKey: `order_${orderId}` });
oura.payments.create(params, idempotency_key=f"order_{order_id}")
$oura->payments->create($params, "order_{$orderId}");
client.Payments.Create(params, fmt.Sprintf("order_%d", orderID))
oura.payments.create(params, "order_" + orderId);
await oura.Payments.CreateAsync(parameters, idempotencyKey: $"order_{orderId}");
oura.payments.create(params, idempotency_key: "order_#{order_id}")

Webhooks are at-least-once

The same idea applies to webhooks. Oura may deliver an event more than once, so your handler must be idempotent: check whether the order is already paid before you mark it paid.