SportAPI Documentation
EN
C Product documentationCoupon API
v1
Service & pricing ↗ Get access ↗
Coupon API / Missed callback recovery

End-to-End Example: Recovery After a Missed Callback

Callbacks speed up result delivery, but they are not the only source of data. The current coupon state is stored in the SportAPI Coupon Settlement System and is available through the Client API.

If a callback was not delivered or processed, the partner must:

restore the endpoint

determine the outage period

retrieve current coupons through the API

idempotently update coupons and bets

perform only missing financial operations

return to the normal callback + polling flow

When This Scenario Is Needed

Use this guide if:

  • the callback endpoint was unavailable;
  • the request exceeded the 10-second timeout;
  • the application returned a temporary HTTP error;
  • the callback was rejected because of an invalid signature or IP filter;
  • the application returned HTTP 200 before reliably storing the data;
  • success: false or an incomplete processed value was returned;
  • all automatic delivery attempts were exhausted;
  • the partner’s internal handler temporarily stopped updating coupons;
  • you need to check whether any settlement results were missed.

First Determine the Type of Problem

SituationAutomatic retry
Timeout or transport errorYes
HTTP 500, 502, 503, 504Yes
HTTP 200 with an empty bodyNo: delivery is considered successful
HTTP 200, success: true, processed = couponCountNo: delivery is considered successful
HTTP 200, success: falseNo: final error
HTTP 200, processed < couponCountNo: final error
HTTP 401 or 403No: final error
HTTP 201, 202, 204No: not considered successful, but not retried automatically
Any other HTTP statusNo: final error

If the handler temporarily cannot store a batch, it must return 500, 502, 503, or 504. Do not return HTTP 200 with success: false expecting a retry.

Automatic Retry Schedule

No more than five HTTP deliveries are attempted after a temporary error:

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

After all attempts are exhausted, automatic delivery of that batch stops. Recover its data through the API read methods.

Step 1. Restore the Callback Endpoint

Check that:

  • the endpoint is accessible externally;
  • production uses HTTPS;
  • the route accepts POST and application/json;
  • the body is read as raw bytes;
  • X-Coupon-Signature is verified before JSON parsing;
  • the correct callback_secret is used;
  • the IP allowlist, if enabled, contains the current address provided by the manager;
  • the transaction is committed before HTTP 200 is returned;
  • the complete handler finishes within 10 seconds;
  • batchId has a unique constraint.

Do not disable HMAC verification to speed up recovery.

If the batch is already fully stored and arrives again with the same batchId, do not repeat processing; return HTTP 200.

Step 2. Determine the Outage Boundaries

Record:

  • the time of the last known successful callback;
  • the time when the endpoint was restored;
  • the duration of the outage;
  • whether fallback polling ran successfully;
  • which coupons remained active or were waiting for a new result;
  • which batchId values had been stored before the outage.

Add a small time margin before the beginning of the period. Overlap is safe when processing is idempotent.

Choosing a Recovery Method

SituationRecommended method
Interruption of no more than 120 minutesGET /api/partner/coupons/calculated
Interruption over 120 minutes, coupon codes knownPOST /api/partner/coupons/results with coupon_ids
Coupons created during the outage must be recoveredPOST /api/partner/coupons/results with dates
One problematic couponGET /api/partner/coupons/get
Unfinished coupons must be reconciledGET /api/partner/coupons/active

All these methods require a Bearer JWT.

Scenario A. The Interruption Did Not Exceed 120 Minutes

Assume:

callback unavailable for 40 minutes
current time: 14:00

Request coupons settled during the last 50 minutes:

BASE_URL="https://coupon-api.example.com"
TOKEN="<jwt-token>"

curl --request GET \
  --url "$BASE_URL/api/partner/coupons/calculated?time=50" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer $TOKEN"

The additional 10 minutes overlap the outage boundary.

This method:

  • filters by final settlement time;
  • does not depend on coupon creation time;
  • does not return active coupons;
  • returns an array of complete models directly in body;
  • limits the window to a maximum of 120 minutes.

Response Example

{
  "code": 1,
  "body": [
    {
      "coupon_code": "000000000272",
      "amount": 10,
      "real_win": 18.5,
      "calculate_coef": 1.85,
      "status": 2,
      "calculate_date": 1784973600000,
      "events_data": [
        {
          "id": 912,
          "status": 1,
          "calc_coef": 1.85,
          "calculate_date": 1784973600000
        }
      ]
    }
  ],
  "error_code": null,
  "error_message": null,
  "date": 1784973600100,
  "time_ms": 12,
  "path": "/api/partner/coupons/calculated"
}

Process every entry in the body array.

An empty array with code = 1 is a successful response and means that no matching coupons exist in the window.

Scenario B. The Interruption Exceeded 120 Minutes

One /calculated request cannot cover a period longer than 120 minutes.

Retrieve coupon codes from the local database for coupons:

  • that were active before the outage;
  • that have no confirmed current final result;
  • that are waiting after status 15;
  • whose state is uncertain.

Split the list into batches of no more than 100 codes.

curl --request POST \
  --url "$BASE_URL/api/partner/coupons/results" \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "coupon_ids": [
      "000000000272",
      "000000000273",
      "000000000274"
    ]
  }'

Response:

{
  "code": 1,
  "body": {
    "query_type": "ids",
    "coupons": []
  },
  "error_code": null,
  "error_message": null,
  "date": 1784973600100,
  "time_ms": 10,
  "path": "/api/partner/coupons/results"
}

Important behavior:

  • unknown coupon codes and codes owned by another client are skipped;
  • response order may differ from coupon_ids;
  • the number of results may be smaller than the number of codes;
  • match results by coupon_code, not by array index.

If Some Coupon Codes Were Not Stored

To recover coupons created during the outage, search by creation period:

curl --request POST \
  --url "$BASE_URL/api/partner/coupons/results" \
  --header "Authorization: Bearer $TOKEN" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "start_date": 1784880000000,
    "end_date": 1784966400000
  }'

Each interval:

  • must be positive;
  • cannot exceed 24 hours;
  • refers to creation time, not settlement time.

For a longer period, make several requests using intervals no longer than 24 hours.

Important. A search by creation date will not find an older coupon merely because it was settled during the outage. Use coupon_ids for older known coupons.

Scenario C. One Problematic Coupon

COUPON_CODE="000000000272"

curl --request GET \
  --url "$BASE_URL/api/partner/coupons/get?coupon_code=$COUPON_CODE" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer $TOKEN"

This method returns one complete current model directly in body.

Use it:

  • when a user contacts support;
  • when the callback and local database disagree;
  • to obtain amount, which is absent from callbacks;
  • for a targeted check after status 15.

Reconciling Active Coupons

After recovery, request:

curl --request GET \
  --url "$BASE_URL/api/partner/coupons/active" \
  --header "Accept: application/json" \
  --header "Authorization: Bearer $TOKEN"

Compare the response with coupons considered active in the local system.

If a completed coupon remains active only in the local database, retrieve it by coupon_code or through /results.

Do not use /active instead of searching for settled results: completed coupons are not included.

Step 3. Apply the Current State Idempotently

API read responses do not contain batchId. They return the current coupon state.

For each coupon:

  1. find the local record by coupon_code;
  2. update the coupon fields;
  3. iterate over all of events_data;
  4. update bets by coupon_code + id;
  5. determine whether a new financial action is required;
  6. commit the data and financial operation in one transaction.

Bet ID correspondence:

API:      events_data[].id   = 912
callback: events_data[].uuid = "912"

Do not create separate records for the same ID merely because its type differs.

Separate Idempotency for Financial Operations

Unique batchId values protect only against repeated delivery of one callback.

The following sequence is possible after recovery:

1. The result is obtained through polling.
2. The partner credits real_win.
3. A callback with a batchId unknown to the partner arrives later.
4. The callback contains the settlement result that was already applied.

If only batchId is checked, the payout will be credited twice.

Financial actions need their own unique key, for example:

coupon_code + settlement_generation + operation_type

Example:

000000000272:0:final_credit

When the same financial state appears again:

  • update any required data;
  • register the new batchId if this is a new callback;
  • do not repeat the debit or credit.

Applying Coupon Statuses

Coupon statusMeaningRecovery action
0The coupon is active or an accumulator is partially settledUpdate the data; do not make a payout.
2WinCredit real_win once if this result has not yet been processed.
4LossRecord the result; the credit is 0.
8ReturnCredit real_win once if this result has not yet been processed.
15Coupon returned for recalculationRun the separate recalculation flow.

Do not determine the result from real_win alone. Always check status.

Recovering Status 15

If the API returns:

coupon status = 15
corresponding bet status = 4

and the previous final result was already processed financially:

  1. check whether the repeated deduction for this transition has already been made;
  2. if not, deduct amount once again;
  3. move the local coupon into a state awaiting a new result;
  4. do not make a final credit while status is 15;
  5. continue checking the coupon by coupon_code;
  6. after the next final status, credit the new real_win once.

If the repeated deduction was already made through a callback or an earlier polling request, do not make it again.

Special Case: Losing Accumulator

An accumulator loss may be known before all bets have been settled:

coupon status = 4
bet statuses = [1, 2, 0]

The financial result is already a loss.

Later, the complete model may become:

coupon status = 4
bet statuses = [1, 2, 1]

Update the last bet, but do not create a second financial loss operation.

If Callback and API States Disagree

Do not determine the current state only by HTTP request arrival order.

For example, an older callback may arrive late after polling. If the states differ:

  1. do not perform a financial action immediately;
  2. request the current model through GET /api/partner/coupons/get;
  3. synchronize the local coupon with the API response;
  4. apply only the missing financial operation.

calculate_date is useful for reconciliation, but the business decision must consider the coupon status, bet statuses, and the settlement cycle already processed.

Step 4. Return to Normal Operation

After reconciliation:

  1. make sure the callback endpoint responds reliably;
  2. verify HMAC on new callbacks;
  3. continue deduplication by batchId;
  4. start regular fallback polling;
  5. use overlapping time windows.

Recommended example:

every 5 minutes:
GET /api/partner/coupons/calculated?time=10

Do not advance the local polling window if a request ends with a transport or server error. First repeat the check successfully with overlap.

How to Confirm Recovery Is Complete

Check that:

  • all coupons in the period are matched by coupon_code;
  • all locally active coupons have been reconciled;
  • every events_data entry is updated;
  • no final statuses remain unprocessed;
  • no status 15 remains without a scheduled follow-up check;
  • every financial operation has a unique record;
  • rerunning the procedure does not change the balance again;
  • new callbacks are accepted and receive HTTP 200;
  • fallback polling is running on schedule again.

The last point is especially important: the recovery procedure must be idempotent. Running it again with the same data must not create new debits or credits.

Common Mistakes

  • Requesting /calculated?time=120 and assuming it covers a multi-day outage.
  • Searching for results only by coupon creation time.
  • Matching a /results response by array position.
  • Processing only the first coupon or first bet.
  • Creating separate records for callback and polling data.
  • Treating every unknown batchId as a new financial result.
  • Crediting a result again after it was already applied through polling.
  • Ignoring status 15.
  • Overwriting a newer state with an older callback.
  • Returning HTTP 200 before reliably storing the batch.
  • Disabling HMAC during emergency recovery.

Short Runbook

1. Fix the callback endpoint.
2. Determine the start and end of the outage.
3. If the outage <= 120 minutes:
       call /calculated with a safety margin.
4. If the outage > 120 minutes:
       request known coupon_code values in batches of up to 100.
5. If necessary, recover created coupons using windows of up to 24 hours.
6. Reconcile /active.
7. Upsert coupons by coupon_code.
8. Upsert bets by coupon_code + id.
9. Apply only missing financial operations.
10. Resume callbacks and overlapping polling.

Checklist

  • The reason for the missed callback has been identified.
  • HMAC verification was not disabled.
  • The outage period is known.
  • A short outage is covered by a window no longer than 120 minutes.
  • A long outage is recovered using known coupon_code values.
  • Requests by code are split into batches of no more than 100 entries.
  • Creation periods are split into intervals of no more than 24 hours.
  • Responses are matched by coupon_code.
  • All coupons and bets are processed.
  • API id is matched with callback uuid.
  • Financial idempotency does not rely only on batchId.
  • Status 15 is handled in a separate cycle.
  • Repeating the procedure does not change the balance.
  • The callback responds with HTTP 200 again.
  • Fallback polling is running again with overlap.

More information:

Next section: Endpoint Reference.