# Amazon SP-API notifications: deduplicate and reconcile

Source: https://ipvolt.com/guides/amazon-listing-notifications
Markdown: https://ipvolt.com/guides/amazon-listing-notifications.md

[Home](https://ipvolt.com/index.md) / [Guides](https://ipvolt.com/guides.md) / [Amazon SP-API notifications: deduplicate and reconcile](https://ipvolt.com/guides/amazon-listing-notifications.md)

Category: Integration
Reviewed: 2026-09-16
Published: 2026-09-16
Reading time: 6 minutes
Author: ipvolt

Handle Amazon SP-API listing events with a tested SQLite inbox: deduplicate deliveries, recover after restart and reject superseded reads.

By ipvolt · Checked 16 September 2026

Persist each validated Amazon listing notification **and the work it creates in one transaction** before acknowledging delivery. Use the notification to schedule a listing read. If another accepted notification arrives during that read, keep the work pending and read again. This prevents a specific local race: an older in-flight result clearing a newer refresh request.

This guide is for developers with an authorized SP-API application and an existing EventBridge delivery pipeline. Integration requires your own validated event adapter and authenticated listing-read adapter. The downloadable SQLite lab runs offline with synthetic events, one reconciliation worker and Python's standard library; it implements the persistence and completion checks between those adapters.

**Method:** ipvolt ran the demo and 12 tests on 16 September 2026 using CPython 3.14.7 and SQLite 3.53.4. Python 3.11+ is the source target; 3.11 was not exercised in that recorded run. No Amazon account, API request or real queue acknowledgment was tested.

## Run the local lab

Download and extract the [complete lab ZIP](https://ipvolt.com/downloads/amazon-listing-notifications/amazon-listing-notifications.zip). Open a terminal in the directory containing `consumer.py`, then run:

```sh
python3 -B demo.py
python3 -B -m unittest -v test_consumer.py
```

There are no packages to install. Both commands use temporary databases. The demo prints deterministic JSON; compare it with the [recorded output](https://ipvolt.com/downloads/amazon-listing-notifications/example-output.json).

The [README](https://ipvolt.com/downloads/amazon-listing-notifications/README.md) documents the complete contract. Inspect [consumer.py](https://ipvolt.com/downloads/amazon-listing-notifications/consumer.py), [demo.py](https://ipvolt.com/downloads/amazon-listing-notifications/demo.py), the [test suite](https://ipvolt.com/downloads/amazon-listing-notifications/test_consumer.py) and [synthetic fixture](https://ipvolt.com/downloads/amazon-listing-notifications/fixtures/scenario.json) individually if you prefer.

## Normalize a trusted event before storing it

Listing status and issues notifications use Amazon's **EventBridge workflow**. An SQS queue can be a downstream rule target; that is different from subscribing through the direct SP-API SQS workflow. Follow Amazon's [EventBridge setup](https://developer-docs.amazon/sp-api/docs/set-up-notifications-with-amazon-eventbridge) for destinations, subscriptions and delivery permissions.

Pin the raw type and payload version in your adapter. The lowercase names below belong to this lab:

| Documented Amazon type | Payload version | Normalized lab type |
| --- | --- | --- |
| `LISTINGS_ITEM_STATUS_CHANGE` | `1.0` | `listing_status_changed` |
| `LISTINGS_ITEM_ISSUES_CHANGE` | `2023-12-13` | `listing_issues_changed` |

Amazon directs users of issues version `1.0` to migrate to `2023-12-13`. Status events concern creation, deletion and buyability; issue events contain summaries that can prompt a fuller read. They do not describe every possible listing problem. [Amazon notification types](https://developer-docs.amazon/sp-api/docs/notification-type-values).

Both raw payloads allow `MarketplaceId` to be absent. Resolve it only through trusted, authorized configuration; otherwise hold the event for investigation before creating a normalized record. Never silently supply your default marketplace. The lab requires explicit seller, SKU and marketplace. See the pinned [status schema](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemStatusChangeNotification.json) and [issues schema](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemIssuesChangeNotification_2023-12-13.json).

There is also a naming inconsistency: that status schema's enum says `LISTINGS_ITEM_STATUS_CHANGED`, while its example and the documentation say `LISTINGS_ITEM_STATUS_CHANGE`. Keep any validation exception narrow, versioned and covered by your adapter tests. The normalized lab neither parses that envelope nor certifies its schema conformance.

This is the exact first synthetic record in the fixture:

```json
{
  "schema_version": 1,
  "source": "fixture:eventbridge:bus-A",
  "subscription": "fixture-subscription-A",
  "notification_id": "notice-001",
  "seller_id": "seller-A",
  "sku": "SKU-RED",
  "marketplace_id": "market-A",
  "notification_type": "listing_status_changed",
  "event_time": "2026-09-16T10:00:00Z",
  "payload": {"hint": "synthetic status change"}
}
```

`Store.ingest()` accepts this object as JSON text. The adapter must validate source, application/subscription context, seller authorization and scope first. Preserve the inner notification identity separately from EventBridge's outer event ID and any queue receipt handle. The [EventBridge envelope](https://docs.aws.amazon.com/eventbridge/latest/ref/events-structure.html) and [SQS deletion contract](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteMessage.html) describe those distinct transport values.

The lab deduplicates `(source, subscription, notification_id)`. This composite is an application policy. Keep delivery-attempt counters, receipt handles and receive times out of the normalized object: they change between deliveries. The README lists its strict field, size and JSON validation bounds.

## Commit acceptance and pending work together

EventBridge can deliver duplicates and provides no ordering guarantee. A downstream SQS standard queue also permits duplicates and out-of-order delivery. [AWS delivery comparison](https://docs.aws.amazon.com/decision-guides/latest/decision-guides/sns-or-sqs-or-eventbridge.html), [SQS standard queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html).

The executable core handles that uncertainty in four steps:

1. Start a SQLite transaction. For a new identity, insert the inbox record and increment the work generation for `(seller_id, sku, marketplace_id)` together.
2. For an existing identity, compare canonical normalized content. Identical content returns `duplicate` without incrementing work. Different content raises `IdentityConflict` and preserves the original state.
3. Return `may_ack=True` only after commit. This is a logical decision for your transport adapter; the download makes zero queue acknowledgment calls. Validation, conflict and storage errors do not produce that decision.
4. Scan `dirty_keys()` after restart and between runs. A key is dirty when `generation > applied_generation`. Capture its generation, perform the read outside the transaction, then save the result only if the key and generation still match.

Every newly accepted identity advances its key, including an event with an older `event_time`. The payload is retained for identity comparison and never applied as current listing state. The inspected [status model](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemStatusChangeNotification.json) defines `EventTime` as a timestamp, not a shared listing revision. This design does not discard work merely because its timestamp is older.

## Observe the restart and in-flight read race

This compact trace is drawn from the recorded demo for `seller-A / SKU-RED / market-A`:

| Case | Generation | Applied generation | Dirty | Observed result |
| --- | --- | --- | --- | --- |
| Process exits after commit | 1 | 0 | true | One inbox row survives |
| Redelivery after restart | 1 | 0 | true | `duplicate`, `may_ack: true` |
| Initial synthetic read | 1 | 1 | false | `read-1` saved |
| Older event arrives | 2 | 1 | true | New refresh required |
| Another event arrives during generation-2 read | 3 | 1 | true | `completion_applied: false` |
| Following read fails | 3 | 1 | true | Previous snapshot retained |
| Later successful refresh | 3 | 3 | false | `read-3` saved |

The generation-2 result cannot clear generation 3. Its rejected `read-2` document never replaces `read-1`; the subsequent failure also leaves work pending. The demo finishes with five inbox rows, three independent clean keys and zero external acknowledgments.

All 12 recorded tests pass. They include a hard child-process exit after commit, injected SQLite failures that roll back inbox and work together, conflicting identities, wrong-key results and the race above. This establishes local process-restart behavior, not power-loss or filesystem-corruption durability.

“Clean” means a read was accepted against the latest **persisted local generation**. It cannot prove that Amazon's response includes the triggering change, that another notification is not delayed, or that delivery was complete. The counter is not an Amazon revision or a distributed exactly-once guarantee.

## Validate the read before clearing work

Your read adapter must choose `getListingsItem` datasets explicitly and validate HTTP status, response content and the intended seller/SKU/marketplace meaning before returning `ReadResult`. Amazon's [listing retrieval tutorial](https://developer-docs.amazon/sp-api/docs/retrieve-details-about-a-listing) explains which datasets answer which questions. It now supports multiple marketplaces in the same region for sellers; this lab deliberately keeps one marketplace per work key.

**The core does not validate a real Amazon response.** It checks the adapter-declared key, JSON bounds and local generation. Even `{}` passes its object-shape check. If required data is missing, the response belongs elsewhere, or the request fails, your adapter must raise instead of returning a success-shaped placeholder. `refresh_once()` then leaves the work dirty and the previous snapshot intact.

For the separate decision about accepted submissions, current offers, inventory and unknown observations, use [Amazon listing updates: accepted is not live](https://ipvolt.com/blog/amazon-listing-update-reconciliation). A successful storage operation cannot make an incomplete observation meaningful.

## Add recovery around the core

Use one reconciliation worker for this example. Its tickets are generation checks, not worker leases. The database retains inbox identities indefinitely; deleting them changes the deduplication horizon. Scheduling, backoff, rate limits, retention and concurrent worker ownership require additional design.

A production pipeline also needs durable handling for malformed or conflicting events, delivery-failure recovery and periodic reconciliation of authorized listing scope. Amazon recommends a [backup retrieval mechanism](https://developer-docs.amazon/sp-api/docs/notifications-api); EventBridge stops retrying after its configured policy is exhausted and supports a [dead-letter queue](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rule-retry-policy.html). A sweep and DLQ recovery are outside this lab. They cover failures an inbox cannot see because the event never reached it.

ipvolt is in development. [Join the early-access list](https://ipvolt.com/#waitlist-closing) for one email when access opens. Nothing else.


## Sources & further reading

- [Set up notifications using the Amazon EventBridge workflow](https://developer-docs.amazon/sp-api/docs/set-up-notifications-with-amazon-eventbridge)
- [Notification Type Values](https://developer-docs.amazon/sp-api/docs/notification-type-values)
- [Listings Item Status Change Notification schema](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemStatusChangeNotification.json)
- [Listings Item Issues Change Notification schema2023-12-13](https://raw.githubusercontent.com/amzn/selling-partner-api-models/3659f96867bfc669aca7a524c2f95744ff0e4478/schemas/notifications/ListingsItemIssuesChangeNotification_2023-12-13.json)
- [AWS service event metadata](https://docs.aws.amazon.com/eventbridge/latest/ref/events-structure.html)
- [DeleteMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteMessage.html)
- [Amazon SQS, Amazon SNS, or Amazon EventBridge?](https://docs.aws.amazon.com/decision-guides/latest/decision-guides/sns-or-sqs-or-eventbridge.html)
- [Amazon SQS standard queues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues.html)
- [Retrieve details about a listing for single or multiple Amazon stores](https://developer-docs.amazon/sp-api/docs/retrieve-details-about-a-listing)
- [Notifications API](https://developer-docs.amazon/sp-api/docs/notifications-api)
- [How EventBridge retries delivering events](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rule-retry-policy.html)

## Related guides

- [Configure a proxy in Python Requests](https://ipvolt.com/guides/python-requests-proxy.md)
- [Troubleshoot proxy timeouts one stage at a time](https://ipvolt.com/guides/proxy-timeout-troubleshooting.md)

## About ipvolt

Examples use generic proxy settings, with links to the original technical documentation. Product-specific behavior must be checked with your provider. ipvolt is still in development.

## Know when access opens.

ipvolt · In development

We’re building proxy infrastructure for developers and data teams. Join the interest list for a heads-up when ipvolt is ready.

Consent: One email when access opens. Nothing else.

[Get early access](https://ipvolt.com/guides/amazon-listing-notifications#waitlist-closing). Use the email form on this page to join the interest list.

[Privacy](https://ipvolt.com/privacy)

