Data sync, order sync and the pitfalls in detail
A resilient Amazon and eBay integration is more than a product export: Magento has to act as the single source of truth for product data, while the marketplace stays authoritative for orders. This article walks through the architecture of a custom-built marketplace integration using Service Contracts, queue-based order sync and rate-limit handling for Amazon SP-API and eBay Trading API.
Table of Contents
- 1. Architecture overview: Magento as the single source of truth
- 2. Product and price sync: feed module and attribute mapping
- 3. Real-time stock sync: race conditions and MSI
- 4. Order sync: turning external orders into Magento orders
- 5. Queue-based architecture instead of synchronous import
- 6. Rate limits and backoff strategies
- 7. Notification-based sync instead of pure polling
- 8. Tax and currency pitfalls
- 9. Conflict resolution and monitoring
- 10. Summary
- 11. FAQ
1. Architecture overview: Magento as the single source of truth
The first design mistake in any Amazon and eBay integration happens before a single line of code is written: the question of which system owns which entity is never answered explicitly. For product data, pricing and categorization, Magento is the only sensible source of truth, since price logic, tax classes, customer group prices and catalog hierarchy all converge here. In this model, Amazon and eBay only ever receive derived data transformed into their respective schemas, never a maintained back-channel on the product level.
For orders, this hierarchy flips: the moment a customer buys on Amazon or eBay, the marketplace becomes the system of record, and Magento takes on the role of a downstream order management and fulfillment system. A marketplace integration that tries to synchronize orders bidirectionally inevitably produces conflicts: who wins when a customer cancels on eBay while Magento has already generated an invoice in parallel? The only workable answer is a clear one-way street per entity, combined with a conflict log for the rare cases where both systems change state at the same time.
This split in data ownership has to be enforced technically, not just agreed on organizationally. In practice that means: one module that, for product and price sync, only reads from Magento and only writes to the marketplace APIs, and a second module that, for order sync, only reads from the marketplace APIs and only writes to Magento. This strict directional separation is the single most important architectural decision in the entire Amazon and eBay integration and prevents the typical race conditions that bidirectional sync approaches create.
2. Product and price sync: feed module and attribute mapping
The product and price sync side of an Amazon and eBay integration technically starts with a feed module that accesses the catalog exclusively through the Service Contract layer, never through direct collection queries or resource model access. ProductRepositoryInterface::getList() combined with SearchCriteriaBuilder returns paginated result sets that can be processed without memory problems even for catalogs with tens of thousands of SKUs. A generator-based export that reads page by page and yields immediately avoids holding the entire exportable catalog in memory.
Amazon and eBay each require their own attribute set per product category, sometimes spanning several hundred fields, which rarely maps one-to-one to internal Magento attributes. A clothing item mandatorily needs fields like item_type_keyword and bullet_point on Amazon plus marketplace-specific size charts, while eBay instead expects Item Specifics with its own category ID structure. An attribute mapper that encapsulates one transformation rule per target category cleanly decouples these requirements from the actual product model and keeps marketplace-specific logic out of the core catalog.
Equally mandatory is a dedicated mapping table between the internal Magento SKU and external marketplace identifiers such as the Amazon ASIN or the eBay item ID. Without this translation layer, an incoming order sync cannot be matched to the correct SKU, and a stock-level data synchronization cannot be run reliably once variants, categories or marketplace configurations change. The mapping table becomes the central translation layer of the entire Amazon and eBay integration and should also store, per channel, the approval status (active, paused, rejected) and the timestamp of the last successful synchronization.
<?php
declare(strict_types=1);
namespace Mironsoft\MarketplaceSync\Service\Feed;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Mironsoft\MarketplaceSync\Api\MarketplaceMappingRepositoryInterface;
use Mironsoft\MarketplaceSync\Api\AttributeMapperInterface;
/**
* Builds a normalized product feed for a single marketplace channel.
* Reads products via the Service Contract layer and maps them to
* marketplace-specific attribute sets (Amazon category tree / eBay item specifics).
*/
class ProductFeedExportService
{
public function __construct(
private readonly ProductRepositoryInterface $productRepository,
private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
private readonly MarketplaceMappingRepositoryInterface $mappingRepository,
private readonly AttributeMapperInterface $attributeMapper,
private readonly int $pageSize = 200,
) {
}
/**
* Streams the product feed for the given marketplace code page by page,
* so large catalogs never have to be loaded into memory at once.
*
* @param string $marketplaceCode e.g. "amazon_de" or "ebay_de"
* @return \Generator<int, array<string, mixed>>
*/
public function export(string $marketplaceCode): \Generator
{
$currentPage = 1;
do {
$searchCriteria = $this->searchCriteriaBuilder
->addFilter('marketplace_export', 1, 'eq')
->setPageSize($this->pageSize)
->setCurrentPage($currentPage)
->create();
$searchResult = $this->productRepository->getList($searchCriteria);
foreach ($searchResult->getItems() as $product) {
$mapping = $this->mappingRepository->getByProductAndChannel(
(int) $product->getId(),
$marketplaceCode
);
// Skip products without an approved external mapping,
// e.g. missing Amazon ASIN or eBay category ID.
if ($mapping === null || !$mapping->isActive()) {
continue;
}
yield $this->attributeMapper->mapToChannel($product, $mapping, $marketplaceCode);
}
$currentPage++;
} while ($currentPage <= (int) ceil($searchResult->getTotalCount() / $this->pageSize));
}
}
3. Real-time stock sync: race conditions and Multi Source Inventory
A data synchronization for stock that runs exclusively via a cron job every 15 minutes works fine as long as available stock per SKU stays well above expected sales volume for that sync interval. But as soon as an item drops to single-digit quantity while selling simultaneously on the own shop, on Amazon and on eBay, a classic race condition emerges: two channels can sell the same last available unit within the same 15-minute window without either channel knowing about the other. The result is overselling, followed by manual cancellations that damage both customer satisfaction and seller rating on both marketplaces.
Magento Multi Source Inventory already provides the technical foundation for capturing stock changes immediately, without recalculating the physical warehouse stock, through the concept of Reservations (Magento\InventoryReservationsApi\Api\ReservationBuilderInterface and AppendReservationsInterface). Every incoming marketplace order creates a reservation that directly reduces the saleable quantity before the actual order creation in Magento has even completed. The push toward Amazon and eBay should therefore not wait for the cron cycle but be triggered event-based the moment the saleable quantity of a mapped product changes.
In practice, a two-tier strategy works best: an event-based push on every relevant stock change keeps latency low for scarce stock, while a cron sync acts as a fallback every few minutes to reconcile discrepancies caused by failed events, API outages or manual corrections in the backend. A pure cron approach without an event trigger is structurally unsuited for low-stock items and should never be the sole mechanism in a production Amazon and eBay integration.
4. Order sync: turning external orders into Magento orders
The order sync is the heart of any Amazon and eBay integration, because it has to turn a marketplace-specific data structure into a fully valid Magento order with correct tax calculation, shipping method and payment status. The clean path runs through CartManagementInterface::placeOrder() on a quote that was populated programmatically beforehand, followed by accessing the resulting order via OrderRepositoryInterface. Writing directly into the sales tables while bypassing the quote-to-order pipeline reliably produces inconsistent orders missing tax lines, shipping information or correct totals.
External order statuses cannot be mapped directly onto Magento statuses, because both systems use different state models. Amazon's Shipped, Unshipped and Canceled, as well as eBay's Active, Completed and Cancelled, need to be mapped onto Magento statuses like processing, complete or canceled via a dedicated status mapping class, with fulfillment-dependent edge cases like Amazon FBA orders (which, when fulfilled by Amazon itself, never generate a shipment notification from Magento) explicitly accounted for. A hardcoded if construct for this mapping turns into a maintenance problem with every new marketplace or fulfillment model, whereas a configurable mapping stored in its own table does not.
Idempotency at the level of the external order number is also essential: both Amazon notifications and eBay notifications can arrive more than once due to network issues or redelivery of the same message. The import service must check, before creating any order, whether the external order number has already been processed, and must not create a second Magento order on a repeat delivery. This check belongs in the same transaction as writing the sync log entry, to rule out race conditions between parallel consumer instances.
<?php
declare(strict_types=1);
namespace Mironsoft\MarketplaceSync\Service\Order;
use Magento\Quote\Api\CartManagementInterface;
use Magento\Sales\Api\OrderManagementInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
use Mironsoft\MarketplaceSync\Api\Data\ExternalOrderInterface;
use Mironsoft\MarketplaceSync\Model\Order\StatusMapper;
use Mironsoft\MarketplaceSync\Model\Order\SyncLogRepository;
use Psr\Log\LoggerInterface;
/**
* Creates a Magento sales order from a normalized external order DTO
* (Amazon SP-API order or eBay Trading API order, already mapped
* to a marketplace-agnostic structure upstream by a QuoteBuilder).
*/
class MarketplaceOrderImportService
{
public function __construct(
private readonly CartManagementInterface $cartManagement,
private readonly OrderRepositoryInterface $orderRepository,
private readonly OrderManagementInterface $orderManagement,
private readonly StatusMapper $statusMapper,
private readonly SyncLogRepository $syncLogRepository,
private readonly LoggerInterface $logger,
) {
}
/**
* Imports a single external order. Returns the created Magento order ID
* or null if the order was already imported (idempotent by external order number).
*
* @param ExternalOrderInterface $externalOrder
* @return int|null
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function import(ExternalOrderInterface $externalOrder): ?int
{
if ($this->syncLogRepository->isAlreadyImported($externalOrder->getExternalOrderNumber())) {
$this->logger->info(sprintf(
'Marketplace order %s already imported, skipping.',
$externalOrder->getExternalOrderNumber()
));
return null;
}
// The quote is built from the external order lines beforehand
// by a dedicated QuoteBuilder that resolves SKUs via the mapping table.
$quoteId = $externalOrder->getPreparedQuoteId();
$orderId = (int) $this->cartManagement->placeOrder($quoteId);
$order = $this->orderRepository->get($orderId);
$order->setData('external_order_number', $externalOrder->getExternalOrderNumber());
$order->setData('marketplace_channel', $externalOrder->getMarketplaceCode());
$mappedStatus = $this->statusMapper->mapExternalStatus(
$externalOrder->getMarketplaceCode(),
$externalOrder->getExternalStatus()
);
$order->setStatus($mappedStatus);
$this->orderRepository->save($order);
$this->syncLogRepository->logSuccess($externalOrder->getExternalOrderNumber(), $orderId);
return $orderId;
}
}
5. Queue-based architecture instead of synchronous import
A synchronous import, where a cron job directly and blockingly performs the entire order creation for every new marketplace order, has one decisive design flaw: a single bad record, say an order referencing an unmapped SKU, can block the entire batch, or at least delay every subsequent order in the same run. For a resilient Amazon and eBay integration, a queue-based architecture built on Magento's message queue framework (RabbitMQ as the default broker) is therefore the right choice.
A dedicated topic such as mironsoft.marketplace.order.imported accepts incoming, already-normalized external orders as soon as a lightweight producer has fetched them from the Amazon or eBay API. A dedicated consumer implementing Magento\Framework\MessageQueue\ConsumerInterface takes over the actual, potentially error-prone order creation. If processing a single message fails, every other message in the queue remains unaffected, and the failed message can be routed specifically into a retry queue or dead-letter queue.
This decoupling also brings operational benefits: the number of parallel consumer processes can be scaled independently of the API fetch rate, and an outage of the Amazon SP-API or the eBay Trading API doesn't block the rest of the sync operation, because messages already fetched keep being processed from the queue. A production-ready message queue configuration needs three declarations working together: the topic in communication.xml, the exchange binding in queue_topology.xml, and the consumer definition in consumers.xml.
<!-- app/code/Mironsoft/MarketplaceSync/etc/communication.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/communication.xsd">
<topic name="mironsoft.marketplace.order.imported" schema="Mironsoft\MarketplaceSync\Api\Data\ExternalOrderInterface"/>
</config>
<!-- app/code/Mironsoft/MarketplaceSync/etc/queue_topology.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/queue_topology.xsd">
<exchange name="mironsoft.marketplace" type="topic" connection="amqp">
<binding id="marketplaceOrderBinding"
topic="mironsoft.marketplace.order.imported"
destinationType="queue"
destination="mironsoft.marketplace.order.import"/>
</exchange>
</config>
<!-- app/code/Mironsoft/MarketplaceSync/etc/consumers.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework-message-queue:etc/consumers.xsd">
<consumer name="mironsoft.marketplace.order.import"
queue="mironsoft.marketplace.order.import"
connection="amqp"
handler="Mironsoft\MarketplaceSync\Model\Queue\OrderImportConsumer::process"
maxMessages="500"/>
</config>
6. Rate limits and backoff strategies for Amazon SP-API and eBay Trading API
Both the Amazon SP-API and the eBay Trading API limit the number of allowed requests using a token bucket principle: a pool of tokens continuously refills at a fixed rate, and every request consumes one token. Once the pool is depleted, the API responds with HTTP 429 (Too Many Requests) or a marketplace-specific error until tokens become available again. An Amazon and eBay integration that fires requests without regard for this budget produces systematic failures under load instead of a stable data synchronization.
The correct response to a 429 is never an immediate retry, but exponential backoff: the wait time between retries doubles after every further failure, combined with an upper bound on the maximum number of attempts. Without that cap, a backoff mechanism risks becoming the cause of an infinite loop in extreme cases, if a structural error rather than a temporary rate-limit issue is at play.
Because Amazon and eBay use different limits, time windows and header formats for rate-limit information, a central API client per marketplace that tracks the current token state itself, rather than relying purely on reactive 429 handling, is worth building. Such a client can throttle proactively before the API even responds, which noticeably reduces the number of unnecessary failed attempts during ongoing order sync and data synchronization.
<?php
declare(strict_types=1);
namespace Mironsoft\MarketplaceSync\Service\Api;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Exception\RequestException;
use Psr\Log\LoggerInterface;
/**
* Central API client wrapper with per-marketplace rate-limit tracking
* and exponential backoff for HTTP 429 / 503 responses.
*/
class RateLimitedApiClient
{
private const int MAX_RETRIES = 5;
private const float INITIAL_BACKOFF_SECONDS = 1.0;
public function __construct(
private readonly ClientInterface $httpClient,
private readonly RateLimitTrackerInterface $rateLimitTracker,
private readonly LoggerInterface $logger,
private readonly string $marketplaceCode,
) {
}
/**
* Executes an HTTP request against the marketplace API, honoring the
* locally tracked token bucket and retrying with exponential backoff
* on 429 (Too Many Requests) or 503 responses.
*
* @param string $method
* @param string $uri
* @param array<string, mixed> $options
* @return array<string, mixed>
* @throws \RuntimeException
*/
public function request(string $method, string $uri, array $options = []): array
{
$this->rateLimitTracker->waitForToken($this->marketplaceCode);
$attempt = 0;
$backoff = self::INITIAL_BACKOFF_SECONDS;
while (true) {
try {
$response = $this->httpClient->request($method, $uri, $options);
$this->rateLimitTracker->recordSuccess($this->marketplaceCode, $response->getHeaders());
return json_decode((string) $response->getBody(), true, flags: JSON_THROW_ON_ERROR);
} catch (RequestException $exception) {
$statusCode = $exception->getResponse()?->getStatusCode() ?? 0;
if (!in_array($statusCode, [429, 503], true) || $attempt >= self::MAX_RETRIES) {
throw new \RuntimeException(
sprintf('Marketplace API call failed after %d attempts: %s', $attempt, $exception->getMessage()),
previous: $exception
);
}
$this->logger->warning(sprintf(
'[%s] Rate limited (HTTP %d), retrying in %.1fs (attempt %d)',
$this->marketplaceCode,
$statusCode,
$backoff,
$attempt + 1
));
usleep((int) ($backoff * 1_000_000));
$backoff *= 2;
$attempt++;
}
}
}
}
7. Notification-based sync instead of pure polling
Pure polling, where a cron job asks Amazon or eBay for new orders at fixed intervals, creates a structural delay between the moment of purchase and visibility in Magento, even with short intervals. For time-critical processes like same-day fulfillment or automated stock alerts, this delay is unacceptable. Amazon offers event-based notifications via SNS (Simple Notification Service), and eBay via its own Platform Notifications, reporting a new order to a registered endpoint nearly in real time.
The endpoint receiving these notifications should not perform synchronous processing itself, but merely validate the incoming payload, transform it into a normalized structure, and hand it off to the queue topic described in Section 5. This decoupling ensures that a brief outage of the Magento consumer does not lead Amazon or eBay to permanently stop notification delivery after several failed attempts.
Notifications, however, do not fully replace the cron sync, they complement it: since delivery guarantees for webhook-based systems are practically never one hundred percent, a cron job remains necessary as a fallback, running at larger intervals, say hourly, specifically looking for orders that don't yet have a corresponding sync log entry. This combination of notification triggers for low latency and a cron fallback for delivery certainty is the most robust approach for order sync in a production Amazon and eBay integration.
{
"notificationType": "ORDER_STATUS_CHANGE",
"marketplace": "amazon",
"eventTime": "2026-07-23T09:14:32Z",
"payload": {
"amazonOrderId": "302-1234567-7654321",
"orderStatus": "Shipped",
"purchaseDate": "2026-07-21T15:03:11Z",
"salesChannel": "Amazon.de",
"fulfillmentChannel": "MFN",
"orderTotal": {
"currencyCode": "EUR",
"amount": "89.90"
},
"orderItems": [
{
"sellerSku": "MS-7742-BLK",
"asin": "B0CXYZ1234",
"quantityOrdered": 2,
"itemPrice": { "currencyCode": "EUR", "amount": "39.95" }
}
]
}
}
8. Tax and currency pitfalls in cross-border trade
VAT treatment of a marketplace order depends heavily on the fulfillment model. For an Amazon FBA order (Fulfillment by Amazon), depending on warehouse location and One-Stop-Shop rules, Amazon itself can act as the party liable for tax (Marketplace Facilitator Tax), while for a self-shipped order (Merchant Fulfilled Network, or eBay orders shipped by the seller), the seller retains tax liability. An Amazon and eBay integration that applies the same tax class and the same tax calculation workflow in Magento to every order ignores this distinction and risks double or missing tax reporting.
A second pitfall, underestimated in practice, is the order of tax calculation: marketplace reports frequently show prices already including tax and rounded at the line-item level, while Magento's tax calculation rounds at the order level by default. For orders with multiple line items and awkward tax rates, this produces rounding differences of a few cents between what the marketplace charges the customer and what Magento computes as the order total. Across many orders, these differences accumulate into a noticeable reconciliation problem in accounting.
On top of that, currency conversion adds complexity, since Amazon and eBay settle at different exchange rates at the time of purchase across their different marketplaces (Amazon.de, Amazon.fr, eBay.co.uk), while Magento typically works with the rate valid at import time if no foreign currency is configured for the same store view. For accurate bookkeeping, the import service should always store the marketplace-reported original amount and currency alongside the converted Magento amount in the sync log, so later discrepancies stay traceable instead of disappearing into tax calculation.
9. Conflict resolution and monitoring: sync log, retry queue and API comparison
Despite careful stock sync and queue-based order sync, conflict cases remain unavoidable: overselling from simultaneous sales across channels, cancellations arriving after an invoice has already been created, and refunds reported at different times between marketplace and Magento. For each of these cases, the Amazon and eBay integration needs a defined conflict strategy rather than an ad-hoc decision made case by case, for example automatic cancellation of the excess sale with customer notification as the default rule for overselling.
A dedicated sync log table that records every sync operation with external reference, timestamp, direction (inbound/outbound), status and, where applicable, an error message is the foundation for any monitoring. Failed synchronizations additionally land in a retry queue with a bounded number of retries and exponential backoff, mirroring the API client from Section 6, so temporary outages don't turn into permanently lost records. Alerting based on this table, for instance when more than a defined number of syncs fail per hour, prevents problems from surfacing only through customer complaints.
The two marketplace APIs differ noticeably on several points that matter for the architecture, which directly affects the implementation effort of an Amazon and eBay integration. The following overview summarizes the key differences that should factor into prioritizing development effort and error handling.
| Criterion | Amazon SP-API | eBay Trading API |
|---|---|---|
| Authentication | LWA (Login with Amazon) OAuth2 plus AWS SigV4 signing | OAuth2 with user and application tokens |
| Rate limits | Token bucket per endpoint, sometimes under 1 request/second in sustained use | Daily quota per call plus burst limits per application |
| Order format | JSON via the Orders resource, nested OrderItems | XML (classic Trading API) with a somewhat legacy structure |
| Notification support | SNS push with a topic subscription per region | Platform Notifications via a webhook endpoint |
| Sandbox availability | Full sandbox environment with generated test data | Sandbox available, behavior sometimes diverges from production |
10. Summary
A resilient Amazon and eBay integration stands or falls on clearly separated data ownership per entity: Magento is authoritative for product, price and stock, the marketplace is authoritative for the order. A feed module built on ProductRepositoryInterface and a mapping layer between internal SKUs and external marketplace IDs form the foundation for data synchronization and price sync, while a queue-based order sync prevents a single bad record from blocking the entire sync batch.
Rate-limit handling with token bucket tracking and exponential backoff, notification-based sync triggers instead of pure polling, and a dedicated sync log table with a retry queue round out the architecture. Anyone who plans these building blocks in from the start, rather than bolting them on afterward, avoids the typical pitfalls around overselling, tax treatment and conflict resolution that regularly turn into support overhead for marketplace integrations patched together after the fact.
Amazon and eBay integration: the key takeaways
Data ownership
Magento is authoritative for product, price and stock, the marketplace is authoritative for the order. Clear directional separation prevents sync conflicts.
Stock sync
Reservations from Multi Source Inventory plus event-based push instead of pure cron prevent overselling on scarce stock.
Queue instead of synchronous import
A dedicated topic and consumer via Magento's message queue framework isolate bad records from the rest of the batch.
Rate limits & monitoring
A central API client with token tracking and exponential backoff, plus a dedicated sync log table with a retry queue for outages.
11. FAQ: Amazon and eBay integration in Magento 2
1Most important architectural principle for Amazon and eBay integration?
2Is cron every 15 minutes enough for stock sync?
3Which Magento classes for order creation?
4Why order import via a queue instead of synchronously?
5How to react to HTTP 429?
6Advantage of notification sync over polling?
7Why is idempotency important for order sync?
8Handling VAT for FBA orders?
9Handling rounding differences?
10What belongs in a sync log table?
Mironsoft
Magento 2 marketplace integration and custom interface development
Need an Amazon and eBay integration for your Magento shop?
We design and build custom marketplace integrations for Magento 2: from product feed and stock sync to queue-based order sync with Amazon SP-API and eBay Trading API, including rate-limit handling and monitoring.
Architecture & design
Data ownership per entity, mapping layer and queue design for your marketplace integration
Feed & order sync
Product and price feed, real-time stock sync and queue-based order import
Monitoring & operations
Sync log, retry queue and alerting for stable day-to-day operations