An abandoned-cart reminder looks like a marketing message, but the engineering problem is a small distributed system. A cart can change after the first event, a webhook can be retried or arrive out of order, a shopper can purchase from another device, and an external message provider can time out after accepting a request. The worst failure is not a missed reminder. It is a reminder sent after payment, or the same reminder sent twice.
Salla's current developer documentation exposes the pieces needed to build a safer workflow. The abandoned-carts API provides a list endpoint and a cart-details endpoint under the `carts.read` scope. The cart webhook models document abandoned-cart creation and update payloads, status changes and a purchased event whose state becomes `purchased`. The changelog records the addition of a `status` field to cart details and the `abandoned.cart.status.changed` and `abandoned.cart.purchased` event models.
This is a current-documentation explainer, not news of a feature launched today. Documentation was verified on 28 September 2026. It also draws an important boundary: these APIs and events expose cart state. They do not, by themselves, prove that a recovery message was delivered. Email, SMS or WhatsApp delivery remains the responsibility of a documented communications product, an installed application or your own integration with an external provider.
Define ownership before writing a worker
Separate the system into three responsibilities. Salla is the source of commerce state: cart identity, contents, status and purchase transitions. Your application owns workflow state: which event was received, whether the cart is eligible, when a reminder may run and why it was suppressed. The delivery provider owns the transport attempt and its evidence.
Do not collapse those states into one `sent` boolean. A durable record should distinguish at least `observed`, `verified_abandoned`, `eligible`, `scheduled`, `dispatched`, `delivered`, `converted`, `suppressed`, `expired` and `review_required`. A provider timeout after dispatch is `unknown`, not permission to send a second message. A cart that later becomes purchased is `converted` or `suppressed`, even if an earlier reminder remains visible in the provider log.
Use a tenant key on every record. The cart identifier alone is not a safe multi-store boundary. Bind the Salla authorization, merchant/store identifier, cart identifier, customer reference and provider account at the application layer, and enforce that binding in database queries and background jobs.
Acknowledge the webhook after durable capture
Salla's webhook security documentation specifies an `X-Salla-Signature` generated with SHA-256 over the raw request body and the webhook secret, and recommends a timing-safe comparison. Verify the signature before parsing or acting on the payload. Keep the exact raw bytes because re-serializing JSON can change the value being signed.
The same documentation says Salla waits roughly 30 seconds and retries a failed delivery three times at about five-minute intervals. That makes the public endpoint an ingestion boundary, not a place to call a message provider. After authentication, insert the event and its payload hash into a durable inbox, commit, and return success quickly. A worker can then hydrate state and schedule work outside the webhook request.
The published model does not promise a universal event ID suitable for every deduplication strategy. Treat deduplication as an application design decision: build a stable key from the store, event type, cart identifier and stable payload fields when available, retain a payload hash for audit, and make the state transition itself idempotent. Duplicate delivery should result in the same cart state, not a second scheduled message.
Before subscribing, query List Webhook Events for the events actually available to the authorized application. Then use Register Webhook with the smallest necessary event set. The registration endpoint documents version 2 as the default and notes that registering the same URL updates or restores an existing webhook. Keep registration reconciliation in deployment automation so a deleted or changed subscription becomes observable.
Hydrate current state; do not trust event age
A webhook says something happened. It does not guarantee that its snapshot is still the latest state when a worker runs. Fetch Abandoned Cart Details before moving from `observed` to `eligible`. Record the cart status you read, the read time and the source event that caused the check.
Eligibility should be explicit and versioned: minimum cart age, customer/contact presence, channel consent, frequency cap, previous recovery attempts, store quiet hours, basket value or merchant-specific exclusions. Store the policy version with the decision. That allows an operator to explain why a message was sent even after policy changes.
Use a delay instead of sending on the first event. The delay absorbs normal browsing, but it also creates a race window. Immediately before dispatch, re-read the cart or consult a state record that is guaranteed to include every processed purchase/status event. If the cart is purchased, missing, expired or no longer allowed, cancel the outbox item. The final stop check is the most important step in the pipeline.
The `abandoned.cart.purchased` and status-change models should drive suppression, not another marketing branch. On either event, cancel every pending delivery for that store and cart, close scheduled retries and preserve an audit reason. Stopping work must be at least as durable as scheduling it.
Add reconciliation for events you never saw
Webhooks reduce latency; they do not remove the need for reconciliation. Use List Abandoned Carts as a periodic repair loop. The endpoint accepts pagination controls and a documented `keyword` filter. Salla's pagination guide documents `per_page` with a general maximum of 60, so persist a cursor or checkpoint and process bounded pages rather than scanning every cart on every run.
Reconciliation should detect four conditions: an abandoned cart missing from your workflow, a local cart whose platform status changed, a scheduled delivery with no recent verification and a local cart that no longer appears where expected. Repair state; do not automatically resend. If the evidence is ambiguous, move the record to `review_required`.
Throttle per store and read the actual rate-limit headers. Salla's rate-limit documentation says limits vary by store plan and exposes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `Retry-After` and `X-RateLimit-Reset`. A scheduler should slow down before exhausting a merchant's allowance, respect `Retry-After`, and prioritize the final pre-send state check over low-value historical scans.
Keep delivery external, idempotent and consent-aware
Put a transactional outbox between the cart workflow and the external delivery provider. In the same database transaction that changes a cart to `scheduled`, insert one outbox row with a stable idempotency key such as store, cart, campaign version and attempt number. The dispatcher claims the row, calls the provider and records the provider request ID. If the process crashes after the call, reconcile by that request ID or idempotency key instead of blindly sending again.
Channel availability is not marketing permission. Keep proof of consent, channel purpose, source and revocation separately, minimize copied personal data and encrypt provider credentials. A webhook secret authenticates Salla; it does not authorize a promotional message to the shopper. Apply the merchant's policy and the legal/contractual requirements for the chosen channel.
Templates should contain no claim that the basket is reserved unless the store truly enforces reservation. Prices, stock and discounts may change. A safer message links the shopper back to the live cart and lets checkout recompute availability and totals.
Test the stop path and measure harm
The happy path is easy. Test duplicate webhooks, invalid signatures, retries after your response is lost, events delivered out of order, purchase during the reminder delay, purchase between the final read and provider call, provider timeout after acceptance, expired credentials, rate-limit responses and two workers claiming the same cart. Inject a reconciliation run after intentionally dropping an event.
Track more than conversion. Useful engineering metrics include webhook verification failures, inbox-to-worker lag, duplicate events suppressed, cart-state read failures, reminders cancelled because of purchase, stale-message rate, provider unknown outcomes, dead-letter depth, reads per recovered cart and customer complaints or opt-outs. Conversion without a stale-send guard is an incomplete success metric.
Roll out with a shadow phase: ingest events, hydrate carts and compute eligibility without sending. Compare the predicted actions with later cart outcomes. Then enable one store or a small cohort, cap attempts per cart and keep a kill switch that disables dispatch without disabling state capture. The reliable design is deliberately conservative: Salla events wake the workflow, current cart state decides, and a durable stop gate always gets the final word.
Official references
These references document the tools discussed. Examples and design decisions are illustrative and should be adapted to the project and its versions.
- Salla Developers — List Abandoned Carts, verified 28 September 2026
- Salla Developers — Abandoned Carts API collection, verified 28 September 2026
- Salla Developers — Cart webhook event models, verified 28 September 2026
- Salla Developers — Webhook security and delivery behavior, verified 28 September 2026
- Salla Developers — Register Webhook, verified 28 September 2026
- Salla Developers — List Webhook Events, verified 28 September 2026
- Salla Developers — API changelog, verified 28 September 2026
- Salla Developers — Pagination, verified 28 September 2026
- Salla Developers — Rate limits, verified 28 September 2026
- Salla Developers — Security, verified 28 September 2026
Prepared by: Noor Yasser
Working through a similar engineering challenge?
I help teams turn architecture decisions into a clear scope and dependable, reviewable implementation.




