Implementation of Stock Synchronization with Dropshipping Supplier
Unsynchronized stock is the main operational problem of dropshipping. Customer places and pays for order, supplier says: item is not in stock. Refund, unhappy customer, reputation loss. Synchronization must run frequently enough to prevent this scenario.
Synchronization Strategies
| Strategy | When to Use | Accuracy |
|---|---|---|
| Full import on schedule | FTP/CSV, no API stock | Low (up to 24h delay) |
| Delta-sync | API with updated_since support |
Medium (15–60 min delay) |
| Realtime webhook | Supplier supports push | High (seconds) |
| Realtime check on add to cart | Any API | High for critical moments |
For most projects, combination: delta-sync every 30–60 minutes + realtime check when adding to cart.
Data Model
Schema::create('dropship_stock_log', function (Blueprint $table) {
$table->id();
$table->foreignId('dropship_product_id')->constrained();
$table->integer('prev_stock');
$table->integer('new_stock');
$table->string('source')->default('sync'); // sync | webhook | realtime_check
$table->timestamp('recorded_at');
$table->index(['dropship_product_id', 'recorded_at']);
});
Full Sync Job
class FullStockSyncJob implements ShouldQueue
{
public $timeout = 1800; // 30 minutes for large catalogs
public function handle(): void
{
$suppliers = Supplier::where('is_active', true)->get();
foreach ($suppliers as $supplier) {
SyncSupplierStockJob::dispatch($supplier)->onQueue('stock-sync');
}
}
}
class SyncSupplierStockJob implements ShouldQueue
{
public $tries = 3;
public $backoff = [60, 300, 900];
public function handle(SupplierConnectorFactory $factory): void
{
$connector = $factory->make($this->supplier);
$page = 1;
$updated = 0;
do {
$products = $connector->getStockLevels($page, 500);
foreach ($products as $item) {
$dropshipProduct = DropshipProduct::where([
'supplier_id' => $this->supplier->id,
'supplier_sku' => $item->sku,
])->first();
if (!$dropshipProduct) continue;
if ($dropshipProduct->supplier_stock !== $item->stock) {
$prevStock = $dropshipProduct->supplier_stock;
$dropshipProduct->update([
'supplier_stock' => $item->stock,
'synced_at' => now(),
]);
// Update store catalog availability
if ($dropshipProduct->product) {
$this->updateProductAvailability($dropshipProduct->product, $item->stock);
}
// Log change
DropshipStockLog::create([
'dropship_product_id' => $dropshipProduct->id,
'prev_stock' => $prevStock,
'new_stock' => $item->stock,
'source' => 'sync',
'recorded_at' => now(),
]);
$updated++;
}
}
$page++;
} while (count($products) === 500);
Log::info('Stock sync completed', [
'supplier' => $this->supplier->slug,
'updated' => $updated,
]);
}
private function updateProductAvailability(Product $product, int $newStock): void
{
$wasAvailable = $product->stock > 0;
$isAvailable = $newStock > 0;
$product->update(['stock' => $newStock]);
// Notify "Notify me" subscribers if item restocked
if (!$wasAvailable && $isAvailable) {
event(new ProductBackInStockEvent($product));
}
}
}
Realtime Check When Adding to Cart
Periodic sync reduces risk but doesn't eliminate it. Add check when adding to cart:
class AddToCartAction
{
public function execute(Product $product, int $quantity, Cart $cart): void
{
// For dropship products check supplier stock
if ($product->dropshipProduct && $this->shouldDoRealtimeCheck($product)) {
$connector = SupplierConnectorFactory::make($product->dropshipProduct->supplier);
$result = $connector->checkStock($product->dropshipProduct->supplier_sku);
// Update cache
$product->dropshipProduct->update([
'supplier_stock' => $result->stock,
'synced_at' => now(),
]);
if ($result->stock < $quantity) {
throw new InsufficientStockException(
available: $result->stock,
requested: $quantity,
);
}
}
$cart->addItem($product, $quantity);
}
private function shouldDoRealtimeCheck(Product $product): bool
{
// Check realtime if last sync was more than 15 minutes ago
$lastSync = $product->dropshipProduct->synced_at;
return !$lastSync || $lastSync->diffInMinutes(now()) > 15;
}
}
Delta-Sync
If supplier API supports updated_since parameter, full import is replaced by delta-sync:
public function getDeltaStock(\DateTimeInterface $since): array
{
$response = $this->http->get($this->endpoint . '/stock', [
'query' => [
'updated_since' => $since->format('c'),
'fields' => 'sku,stock,updated_at',
],
'headers' => $this->authHeaders(),
]);
return json_decode($response->getBody(), true)['items'] ?? [];
}
Webhook from Supplier
If supplier supports push notifications on stock changes:
// routes/api.php
Route::post('/webhooks/supplier/{supplier:slug}/stock', SupplierStockWebhookController::class)
->middleware('verify.supplier.signature');
class SupplierStockWebhookController
{
public function __invoke(Request $request, Supplier $supplier): JsonResponse
{
foreach ($request->input('items', []) as $item) {
UpdateDropshipStockJob::dispatch($supplier, $item['sku'], $item['stock']);
}
return response()->json(['ok' => true]);
}
}
Schedule
// Full sync — once a day (at night)
$schedule->job(FullStockSyncJob::class)->dailyAt('03:00')->withoutOverlapping();
// Delta-sync — every 30 minutes
$schedule->job(DeltaStockSyncJob::class)->everyThirtyMinutes()->withoutOverlapping();
Timeline
Full sync on schedule — 2 business days. Delta-sync + realtime check on add to cart — 3–4 business days. Webhook integration (if supported) — another 1 day.







