Reference
Idempotency and retries
How to make a retry safe, and what Harbour retries for you.
Networks fail in the worst possible place: after your request arrived and before its answer got back. Idempotency is how you retry that safely, and it is on by default.
Idempotency keys
Every call that creates something — charge, refund, links()->create — sends an
Idempotency-Key header. If you do not pass one, the SDK generates a random key for that call, which makes
its own automatic retries safe.
Harbour stores the first answer against the key. Send the same key with the same body and you get that first answer back, without a second charge.
Passing your own
Pass your own key whenever the retry might come from somewhere else: a queue worker picking a job back up, a customer double-clicking, a deploy that replays a request.
PHP
<?php
// one key per payment attempt, derived from something you already store
Harbour::charge(12_000_00, 'funke@example.ng', [
'idempotency_key' => 'order-1043-attempt-' . $order->attempts,
]);
$harbour->charge()->amount(12_000_00)->customer('funke@example.ng')
->idempotencyKey('order-1043-attempt-' . $order->attempts)
->create();
Harbour::refund('HB-88402', null, 'Customer cancelled', [
'idempotency_key' => 'refund-order-1043',
]);
Keys are 1 to 120 printable ASCII characters. Make them mean something: the order id and the attempt number beat a random string, because you can look one up when a support ticket arrives.
What the SDK retries
- At most two retries, with exponential backoff and jitter (about 0.5s then 1s, capped at 5s,
honouring a numeric
Retry-After). - Only for network errors, 5xx and 429.
- Only on GET requests, or requests carrying an idempotency key — which is all of them, since the SDK adds one.
- Every retry reuses the same key, so a retry can never become a second charge.
- 4xx errors other than 429 are never retried: they will not get better.
PHP
<?php
use Nordaxiz\HarbourConnect\HarbourClient;
$harbour = new HarbourClient($key, null, [
'max_retries' => 2, // 0 to 2, default 2
'timeout' => 30, // seconds, default 30
'connect_timeout' => 10,
]);
Set max_retries to 0 when your own queue already handles retries and you would rather it
owned the decision.
Conflicts
Reusing a key with a different body raises IdempotencyConflictException
(409 idempotency_conflict). That is Harbour refusing to guess which of the two you meant. It almost always
means a key is derived from too little — 'order-' . $order->id when the amount can change, for example.
Put everything that can vary into the key, or use the attempt number.