Skip to main content

Overview

All POST requests in the HopNow API require an Idempotency-Key header to prevent duplicate operations. This ensures safe retries when network errors or timeouts occur.
Do not include an Idempotency-Key with GET, PUT, PATCH, or DELETE requests. These methods are naturally idempotent.

How It Works

  • Include a unique Idempotency-Key (UUID format) in every POST request
  • If a request is retried with the same key, HopNow returns the original response without performing the operation again
  • This prevents duplicate payments, account creations, or other operations

Request Example

curl -X POST https://apis.hopnow.io/v1/transfers/payouts \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_api_key" \
  -H "X-Signature: hmac_signature" \
  -H "X-Timestamp: 1234567890" \
  -H "X-Nonce: abc123..." \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "account_id": "acct_123",
    "beneficiary_id": "bene_456",
    "amount": "1000.00",
    "currency": "USD"
  }'

Generating Keys

Use UUIDs for each new operation:
import uuid

idempotency_key = str(uuid.uuid4())

Best Practices

Store Keys in Your Database

Track idempotency keys with request state for reliable retry logic:
# Store key before making request
transaction = {
    "idempotency_key": str(uuid.uuid4()),
    "status": "pending",
    "request_data": payout_data
}
db.save(transaction)

# Make request with stored key
response = client.post(
    "/v1/transfers/payouts",
    json=payout_data,
    headers={"Idempotency-Key": transaction["idempotency_key"]}
)

Retry with Same Key

Always reuse the same idempotency key when retrying failed requests:
def create_payout_with_retry(payout_data, max_retries=3):
    idempotency_key = str(uuid.uuid4())

    for attempt in range(max_retries):
        try:
            response = client.post(
                "/v1/transfers/payouts",
                json=payout_data,
                headers={"Idempotency-Key": idempotency_key}
            )
            return response.json()
        except TimeoutError:
            if attempt == max_retries - 1:
                raise
            time.sleep(2 ** attempt)  # Exponential backoff

Common Errors

Invalid Format

{
  "error": {
    "type": "validation_error",
    "code": "invalid_idempotency_key",
    "message": "The idempotency key must be a valid UUID."
  }
}
Solution: Use a properly formatted UUID (e.g., 550e8400-e29b-41d4-a716-446655440000)

Conflicting Request Body

{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_key_conflict",
    "message": "The request body does not match the original request for this idempotency key."
  }
}
Solution: Use the same request body as the original request, or generate a new idempotency key for a different operation.