SportAPI Documentation
EN
C Product documentationCoupon API
v1
Service & pricing ↗ Get access ↗
Coupon API / Retries and idempotency

Callback Retries and Idempotency

The same callback may be delivered more than once. This is a normal part of reliable delivery, not an error in the SportAPI Coupon Settlement System.

For example, the partner may successfully store the batch, but its HTTP 200 response may be lost in transit. The system cannot know whether the request was processed, so it sends the same batch again.

The handler must be idempotent: a repeated batch must not modify coupons, bets, or balances again.

Two reasons why a coupon may appear again

It is important to distinguish:

Situationcoupon_codebatchIdRequired action
Repeated delivery of the same HTTP batchSameSameDo not repeat business operations; return HTTP 200.
New settlement state of the couponSameNewUpdate the coupon and bets as a new version.

The same coupon_code may legitimately arrive several times with different batchId values, especially for an accumulator.

Purpose of batchId

batchId identifies a specific callback batch version.

The following is guaranteed:

  • repeated HTTP delivery of one batch retains the same batchId;
  • a new settlement state receives a new batchId;
  • one batch may contain up to 100 coupons;
  • batchId is not a coupon or bet ID.

Create a unique index on batchId in the incoming callback table.

Example:

CREATE TABLE coupon_callback_batches (
    batch_id VARCHAR(64) PRIMARY KEY,
    received_at TIMESTAMP NOT NULL,
    coupon_count INTEGER NOT NULL
);

This is only a structure example. The partner chooses the table names and data types.

Why coupon_code is not sufficient

Do not use coupon_code as the unique delivery ID:

coupon_code = 000000000272
batchId = version_A

coupon_code = 000000000272
batchId = version_B

Both versions belong to the same coupon but contain different bet states. If the second version is rejected as a duplicate based on coupon_code, the local record will not be updated.

Use:

  • batchId to deduplicate HTTP batches;
  • coupon_code to find and update a coupon;
  • uuid to update a specific bet within the coupon.

Transactional processing

Registering batchId, updating coupons, and applying financial operations must be performed in one transaction.

Recommended algorithm:

  1. verify the HMAC signature;
  2. parse the JSON;
  3. validate couponCount and the coupons array;
  4. begin a transaction;
  5. attempt to insert batchId with a unique constraint;
  6. if batchId already exists, do not repeat business operations;
  7. process every entry in coupons;
  8. upsert coupons by coupon_code;
  9. upsert bets by the combination coupon_code + uuid;
  10. apply the required financial operations;
  11. commit the transaction;
  12. return HTTP 200.

Simplified flow:

BEGIN

INSERT batchId
    if it already exists:
        COMMIT
        return 200

FOR EACH coupon:
    UPSERT coupon BY coupon_code

    FOR EACH event:
        UPSERT event BY coupon_code + uuid

    APPLY financial changes idempotently

COMMIT
return 200

Do not commit batchId in a separate transaction before storing the coupons. If the application stops after recording batchId but before updating the coupons, a repeated delivery will be incorrectly treated as fully processed.

Atomic batch processing

A batch may contain multiple coupons. HTTP 200 means that the partner successfully committed the entire batch.

If any entry cannot be stored:

  • roll back the transaction;
  • do not return HTTP 200;
  • return a temporary server status for which a retry is supported.

If the architecture cannot update all systems in one transaction, first store the incoming batch atomically in the partner’s internal inbox queue, then process the business operations from that queue idempotently.

Idempotency of financial operations

A unique batchId protects against repeated batch delivery, but financial operations require additional protection at the coupon level.

The reason is that one coupon may receive several different batchId values, such as intermediate and final snapshots.

The partner must separately record:

  • whether the current final result has already been processed;
  • whether the repeated debit on transition to status 15 has already been performed;
  • whether the new realWin after recalculation has already been credited.

One possible approach is to store a local coupon settlement generation number:

coupon_code = 000000000272
settlement_generation = 0

On the first transition to status 15, increment the number:

settlement_generation = 1

Unique keys for financial operations can then be formed as follows:

000000000272:1:recalculation_debit
000000000272:1:final_credit

This protects the balance not only from a repeated batchId, but also from repeated processing of the same state in different parts of the system.

The partner chooses the specific implementation. The main rule is that one settlement transition must create no more than one financial operation of each type.

When retries occur

Delivery is retried after:

  • a timeout;
  • a connection failure or another transport error;
  • HTTP 500;
  • HTTP 502;
  • HTTP 503;
  • HTTP 504.

These conditions are considered temporary.

Retry schedule

The first delivery is attempted immediately. After a failure, the following intervals are used:

1st attempt: immediately
2nd attempt: after 1 minute
3rd attempt: after 5 minutes
4th attempt: after 15 minutes
5th attempt: after 1 hour

No more than five HTTP delivery attempts are made, including the first one.

After all attempts are exhausted, automatic delivery stops. For recovery and reconciliation, use the coupon read methods described in Polling fallback.

Timeout

The complete callback HTTP request is given no more than 10 seconds.

The partner handler must have time to:

  1. verify the signature;
  2. store the batch atomically;
  3. return HTTP 200.

Perform long-running additional operations only after the batch has been stored reliably in the partner’s internal queue.

If the endpoint processes the batch but does not return a response in time, the request may be delivered again with the same batchId.

Behavior for HTTP responses

Response or conditionDelivery behavior
HTTP 200, empty bodySuccess.
HTTP 200, success: true, processed = couponCountSuccess.
HTTP 200, success: falseFinal error without an automatic retry.
HTTP 200, processed < couponCountFinal error without an automatic retry.
HTTP 401Invalid or missing signature; final error.
HTTP 403Request rejected by IP filtering; final error.
HTTP 500, 502, 503, 504Temporary error; delivery is retried.
Timeout or transport errorDelivery is retried.
Any other HTTP statusFinal error without an automatic retry.

The current contract requires HTTP 200 specifically. Responses 201, 202, and 204 are not considered successful and are not retried automatically.

Responding to a temporary error

Do not return HTTP 200 with this body:

{
  "success": false
}

This response is treated as final and does not start a retry.

If the batch cannot be stored temporarily, return a supported temporary HTTP status, for example:

HTTP/1.1 503 Service Unavailable

After the endpoint recovers, the system retries delivery according to the schedule.

Successful acknowledgement

Minimum response:

HTTP/1.1 200 OK

The body may be empty.

Recommended extended response:

{
  "success": true,
  "processed": 3
}

processed must equal the received couponCount.

For a repeated batchId that has already been fully processed, also return HTTP 200. Do not repeat storage or financial operations.

Common integration errors

  • A unique constraint exists only on coupon_code.
  • batchId is stored before the business data in a separate transaction.
  • HTTP 200 is returned before the transaction is committed.
  • Funds are credited again for a repeated batchId.
  • Every new batchId is automatically treated as a new financial result.
  • Only coupons[0] is processed, while the remaining batch entries are skipped.
  • A temporary error returns success: false with HTTP 200.
  • The endpoint returns 204 even though the contract requires 200.
  • Long-running business processing exceeds the timeout and causes a retry.

Checklist

  • batchId has a unique constraint.
  • A repeated batchId returns HTTP 200 without repeated processing.
  • One coupon_code may have multiple versions.
  • The entire coupons array is processed atomically.
  • Coupons are updated by coupon_code.
  • Bets are updated by coupon_code + uuid.
  • Financial operations have their own unique keys.
  • HTTP 200 is returned only after reliable storage.
  • A temporary error returns 500, 502, 503, or 504.
  • success: false is not used to request an automatic retry.

Next section: Polling fallback.