SearchLoyaltyRewards returns abnormal response for a single merchant

Summary

When a customer redeems a loyalty reward at the POS, we needed to detect how much points the customer has used.

We have been able to do so via the following steps.

  1. Receive an order.created webhook.
  2. Resolve the Square customer from the order.
  3. Look up the customer’s loyalty account.
  4. Call SearchLoyaltyRewards with status = REDEEMED for that loyalty account.
  5. Derive the redeemed points from the returned reward(s).

However, for a single merchant, response data for SearchLoyaltyRewards does not look right(differs from other merchants), and we cannot derive how much points were redeemed.

Usual response for SearchLoyaltyRewards

A normal redemption produces two loyalty events, both carrying a reward_id, and SearchLoyaltyRewards returns the reward:

{
  "events": [
    {
      "type": "REDEEM_REWARD",
      "redeem_reward": {
        "loyalty_program_id": "PROGRAM_ID",
        "reward_id": "REWARD_ID",
        "order_id": "ORDER_ID"
      }
    },
    {
      "type": "CREATE_REWARD",
      "create_reward": {
        "loyalty_program_id": "PROGRAM_ID",
        "reward_id": "REWARD_ID",
        "points": -100,
        "order_id": "ORDER_ID"
      }
    }
  ]
}

Abnormal response for SearchLoyaltyRewards

For one merchant, a redemption clearly happened — the loyalty account balance dropped by 100 points (139 → 39) within ~3 seconds of the POS checkout, and the order shows a matching discount. The store staff redeemed the reward using the standard Square POS “redeem a reward” flow (selecting a reward from the loyalty program); we have confirmed this via a recording of the POS operation. However:

  • SearchLoyaltyRewards(loyalty_account_id, status = REDEEMED) returns an empty result ({}), both immediately after checkout and ~6 hours later.
  • The loyalty events for that order contain a REDEEM_REWARD event without a reward_id, and there is no corresponding CREATE_REWARD event at all:
{
  "events": [
    {
      "type": "REDEEM_REWARD",
      "redeem_reward": {
        "loyalty_program_id": "PROGRAM_ID",
        "order_id": "ORDER_ID"
      }
    },
    {
      "type": "ACCUMULATE_POINTS",
      "accumulate_points": {
        "loyalty_program_id": "PROGRAM_ID",
        "points": 12,
        "order_id": "ORDER_ID"
      }
    }
  ]
}

Because no reward is returned by SearchLoyaltyRewards and the REDEEM_REWARD event has no reward_id, we have no reliable way to attribute the redeemed points to the order.

Questions

  1. The customer used the standard “redeem a reward” flow at the POS, and a redemption clearly occurred (the balance dropped by the redeemed points). What could be the reason the API returns this abnormal response?
  2. When a REDEEM_REWARD event has no reward_id, how can we determine how many points were redeemed for that specific order via the API?
  3. Is there a better way to derive used points for a order?

Environment

  • Region: Japan
  • API: Loyalty API (Square Connect v2)
  • The seller has an active Square Loyalty subscription.
  • We can share the specific loyalty_account_id / order_id privately if needed.

Thanks for the detailed writeup. This may come down to which flow performed the redemption.

First, check the source field on that REDEEM_REWARD event. Per LoyaltyEventRedeemReward, reward_id “is returned only if the event source is LOYALTY_API.” A redemption done natively in Square POS has source: SQUARE and no reward_id, whereas one made through CreateLoyaltyReward/RedeemLoyaltyReward does carry it. If that’s the difference between this merchant and your others, it’s about which flow was used rather than a merchant-level fault. Could you share the source value from a working and a non-working case?

This is also related to your earlier thread, Search Loyalty Rewards API returns an empty response, where the team confirmed rewards originating outside the Loyalty API aren’t returned by SearchLoyaltyRewards and recommended SearchLoyaltyEvents. I don’t have a timeline to share on that, so I’d build against SearchLoyaltyEvents.

Second, the missing CREATE_REWARD might be a filter artifact. REDEEM_REWARD has no points field; the debit sits on CREATE_REWARD, which has no order_id field — so an order_filter query can’t return it. The two events you posted are exactly the types that do carry order_id. Worth re-querying SearchLoyaltyEvents by loyalty_account_filter plus a date_time_filter around checkout. Also watch for DELETE_REWARD, since POS lets staff add and remove rewards before tender. In the examples I’ve seen, create_reward.points is negative and delete_reward.points positive, so confirm the signs in your own data before summing.

If you were already querying by account and time window and there’s still no CREATE_REWARD, that would be a genuine gap — send the loyalty_account_id and order_id and I will dig in further.

On deriving used points: check Order.rewards[] on the order each entry has a reward_tier_id that maps to reward_tiers[].points from RetrieveLoyaltyProgram. Failing that, loyalty events can be correlated on loyalty_account_id, created_at and location_id, though there’s no true join key, so multiple redemptions in a short window may be ambiguous.

Thanks @ashley-square for looking into this matter. Answering your question first: the source value is the same: SQUARE in both cases, so it seems the source is not what separates them.

You were right about the CREATE_REWARD being a filter artifact. I tried querying SearchLoyaltyEvents by loyalty_account_filter, and found a CREATE_REWARD event, which includes the used points but does not include an order_id.

{"type": "CREATE_REWARD", "created_at": "2026-08-03T09:23:37Z",
 "create_reward": {"loyalty_program_id": "PROGRAM", "points": -100},
 "loyalty_account_id": "ACCOUNT", "location_id": "LOCATION", "source": "SQUARE"}

On deriving used points:

check Order.rewards[] on the order each entry has a reward_tier_id that maps to reward_tiers[].points from RetrieveLoyaltyProgram.

I checked RetrieveOrder for the Order.rewards[] on the latest API version but the response did not include rewards field. However, for the other merchants, rewards field existed and I was able to find the corresponding tier in RetrieveLoyaltyProgram.


So, my question would be:

Given that both are source: SQUARE, what else can produce the current difference (including the existence of Order.rewards[] field) between the merchants?


BTW, in the “abnormal” case,CREATE_REWARD is created 36 seconds before the REDEEM_REWARD (09:23:37 vs 09:24:13), which per our logs is also before the order itself was created (09:24:09). In the working case, both CREATE_REWARDand REDEEM_REWARD are created around the same time.