Guides

Integration Flows

Setup, purchase, POS and discount flows end-to-end.

Store integrations — recommended flow

1. Prerequisites

  1. Obtain base URL, API key, and required scopes from Sendy.
  2. Confirm the key is linked to your store (otherwise expect 400 from the service layer).
  3. Store the key as a secret (env / vault), not in client-side code.
  4. Cache or call GET /api/v1/address-reference/provinces and GET /api/v1/address-reference/provinces/{code}/areas as needed (no API key) so you can send valid addressProvinceCode / addressAreaId on order create — see api-design.jsonaddressReference.

2. Orders

  1. Create: POST /orders (or POST /orders/bulk for batches). Set fulfillmentType to match how you fulfill (from_to vs warehouse). Send addressProvinceCode and addressAreaId from GET /api/v1/address-reference/provinces and GET /api/v1/address-reference/provinces/{code}/areas. Send customerPhone as a valid Iraq mobile (see IraqPhoneValidation in code). Persist returned id and publicId in your system.
  2. Track: GET /orders/{orderId} for status, payment snapshot, fulfillmentType, and addressProvinceCode / addressAreaId.
  3. Adjust delivery details: PUT /orders/{orderId} if the free-text address or map pin needs to change. The request body is still validated like create (including province, area, and phone), but the server only updates customerAddress, deliveryLat, and deliveryLng.
  4. Cancel: POST /orders/{orderId}/cancel with optional reason.

3. Catalog and inventory

  1. Products: POST /catalog/products to upsert by SKU; GET /catalog/products to reconcile.
  2. Stock: PUT /catalog/inventory/{inventoryItemId} with quantityOnHand; GET /catalog/inventory for full list (includes warehouseId when relevant).

4. Returns

  1. After delivery, POST /orders/{orderId}/return-request with optional reason.
  2. Poll GET /orders/{orderId}/return-requests or GET /return-requests/{returnRequestId} for status (pending, approved, rejected, completed).

5. Errors and retries

  • 400 — validation, bad filter, or business rule; inspect errors and message; fix payload before retrying.
  • 403 — missing scope; fix key configuration.
  • 404 — wrong id or resource not visible to this store.
  • 409 — conflict (e.g. duplicate pending return).
  • 5xx — transient; retry with backoff and idempotency awareness on creates (Sendy does not document a separate idempotency key header; treat externalRef as your own deduplication key on the client side if needed).

For exact JSON properties, always refer to api-design.jsondtos.

This flow is for stores that want a shareable public URL to paste in their Instagram bio or WhatsApp — customers visit the link, browse products, and place orders without any app login.

  1. Add prices and images to catalog products (optional but recommended):

    • PUT /catalog/products/{productId} via the integration API, or store JWT PUT /api/v1/store/products/{id}.
    • Products without a price are still accepted at checkout (line total = 0).
  2. Customize storefront settings (store JWT, store.storefront.manage):

    • PUT /api/v1/store/storefront with description, coverImageUrl, niche.
    • Apply a niche template if desired: GET /api/v1/store/storefront/templatesPOST /api/v1/store/storefront/templates/{id}/apply.
  3. Publish the storefront:

    • POST /api/v1/store/storefront/publish.
    • Returns { slug, is_published: true } and the publicUrl.
    • Fails with 400 if the store has no slug (contact support to assign one; demo stores have slugs seeded automatically).
  4. Share the public URL:

    • Format: {baseUrl}/api/v1/public/storefronts/{slug} (e.g. …/flora-baghdad).
    • Paste in Instagram bio, WhatsApp link, or any social post.
  5. Customer flow (no auth):

    • GET /api/v1/public/storefronts/{slug} — browse store info + active priced products.
    • POST /api/v1/public/storefronts/{slug}/orders — place an order by SKU + quantity.
    • Resulting order has source = "Storefront"; it appears in the store's normal order list.
  6. Unpublish when needed: DELETE /api/v1/store/storefront/publish.

  1. Register endpoint: POST /webhooks with your HTTPS URL.
  2. Persist returned subscription id and keep the webhook secret secure.
  3. On each delivery, verify X-Sendy-Signature against the exact raw body (HMAC SHA-256).
  4. De-duplicate by X-Sendy-Event-Id (and/or event_id) and process asynchronously.
  5. Return 2xx only after persistence/queueing; non-2xx triggers retries with backoff.
  6. Rotate secret using POST /webhooks/{subscriptionId}/rotate-secret during routine key rotation.

8. Store Setup Flow

Use this flow when onboarding a new store before it starts operating.

  1. Initialize settings (required first):
    • POST /api/v1/store/settings with currencyCode, timezone, industry, countryCode, language.
    • Returns 409 if settings already exist.
  2. Configure storefront (optional):
    • PUT /api/v1/store/storefront with extended fields: description, coverImageUrl, bannerImageUrl, contactEmail, social links, SEO fields, FAQs, policies, customDomain.
    • Apply a niche template: POST /api/v1/store/storefront/templates/{id}/apply.
  3. Add package sizes (optional, for shipping):
    • POST /api/v1/store/package-sizes — mark one as isDefault: true.
  4. Configure cashier (optional, for POS):
    • PUT /api/v1/store/cashier/settings with requirePin, openingCashDefault, receiptFooterNote.
  5. Add product categories (optional):
    • POST /api/v1/store/categories — supports parent/child hierarchy via parentCategoryId.
  6. Publish storefront (when ready):
    • POST /api/v1/store/storefront/publish.

9. Purchase Flow

Use this flow to track procurement from suppliers.

  1. Add supplier (optional):
    • POST /api/v1/store/suppliers with fullName, contact details.
  2. Create Purchase Order (starts as Draft):
    • POST /api/v1/store/purchase-orders with supplierId, orderDate, items[] (min 1 item required).
    • Server computes subTotal and totalAmount.
  3. Advance status as PO progresses:
    • PUT /api/v1/store/purchase-orders/{id} with status: "Sent" / "PartiallyReceived" / "Received" / "Cancelled".
  4. Attach supporting documents (max 3):
    • POST /api/v1/store/purchase-orders/{id}/attachments with fileName, fileUrl, fileSize.
  5. Log the expense after payment:
    • POST /api/v1/store/expenses with name, amount, expenseType, category, expenseDate.

10. POS / Cashier Flow

Use this flow for point-of-sale sessions.

  1. Pin frequently sold products (setup step):
    • POST /api/v1/store/cashier/pinned with storeProductId, sortOrder. Max 30 items.
    • Reorder with PUT /api/v1/store/cashier/pinned/reorder.
  2. Open a session at start of shift:
    • POST /api/v1/store/cashier/sessions/open with openingCash amount.
    • Returns 409 if a session is already open.
  3. Sell (during session):
    • Create orders via the normal POST /api/v1/store/orders flow.
  4. Close session at end of shift:
    • POST /api/v1/store/cashier/sessions/{sessionId}/close with closingCash, optional notes.
  5. Review history:
    • GET /api/v1/store/cashier/sessions for all past sessions.

11. Discount Flow

Discount Codes

  1. Create a code:
    • POST /api/v1/store/discounts/codes with code (auto-uppercased), discountType, discountValue, optional minOrderValue, maxUses, startsAt, expiresAt.
  2. Validate at checkout:
    • GET /api/v1/store/discounts/codes/validate?code=SAVE20&orderValue=50000
    • Returns { isValid, discountType, discountValue } or { isValid: false, errorMessage }.
  3. Apply the discount server-side when creating the order (pass the validated code; service deducts from order total).

Discount Rules

  1. Create a rule for automatic discounts (no code required):
    • POST /api/v1/store/discounts/rules with name, discountType, discountValue, optional categoryId (per-category rule), channels.
    • Rules with isActive: true are applied automatically during order processing.

Shipping Rules

  1. Create a rule:
    • POST /api/v1/store/shipping/rules with name, ruleType (FlatRate, FreeShipping, WeightBased, OrderValueBased), cost, optional minOrderValue/maxOrderValue.
    • Rules with isActive: true are evaluated during checkout to determine shipping cost.

12. Social Messaging Agent Setup

Use this flow to connect WhatsApp or Instagram and enable the AI-powered CRM inbox.

A — Connect a channel

  1. Connect the account (store.social_channels.manage):

    • POST /api/v1/store/social/channels
    • Required: channelType (WhatsApp or Instagram), externalAccountId, displayName, accessToken, aiProviderCode, aiModelCode.
    • Optional: appSecret (strongly recommended for webhook HMAC verification), systemPromptOverride, isEnabled, isAgentEnabled.
    • Server encrypts accessToken and appSecret; returns webhookVerifyToken — copy it before leaving this screen.
  2. Register the webhook with Meta (one-time, in Meta Developer Console):

    • Callback URL: https://<your-host>/api/v1/webhooks/social/whatsapp (or /instagram)
    • Verify Token: the webhookVerifyToken from the connect response
    • Subscribe to: messages, message_deliveries, message_reads
    • Meta calls GET /api/v1/webhooks/social/whatsapp?hub.mode=subscribe&hub.verify_token=...&hub.challenge=...; Sendy responds with the challenge to confirm.
  3. Rotate the verify token if compromised:

    • POST /api/v1/store/social/channels/{channelId}/rotate-token — generates a new WebhookVerifyToken; update the Meta webhook registration with the new value.

B — Manage AI agent

  1. Toggle the AI agent on/off per channel:

    • POST /api/v1/store/social/channels/{channelId}/toggle-agent { "enabled": true/false }
    • When disabled, inbound messages are still received and stored but no AiMessageJob is created — staff must reply manually.
  2. AI dispatch (automatic, background):

    • A Hangfire job (social.ai.dispatch) runs every minute.
    • It picks pending AiMessageJob records, calls the configured AI provider (OpenAI or Anthropic) with the conversation history + store context as system prompt, sends the reply via the Meta API, and records token usage.
    • Failed jobs retry with exponential backoff; Dead status is set after MaxAttempts.

C — Staff CRM inbox

  1. View conversations (store.conversations.view):

    • GET /api/v1/store/social/conversations?status=Open&page=1
    • Returns paginated list of conversations. Filter by channelId or status (Open, Pending, Resolved).
  2. Read full conversation (store.conversations.view):

    • GET /api/v1/store/social/conversations/{conversationId}
    • Returns conversation metadata plus the full message history (inbound + outbound, AI-generated or manual).
  3. Send a message manually (store.conversations.manage):

    • POST /api/v1/store/social/conversations/{conversationId}/messages { "text": "..." }
    • Calls Meta API in real time; persists SocialMessage record on success.
  4. Resolve / reopen (store.conversations.manage):

    • POST /{conversationId}/resolve — sets status to Resolved.
    • POST /{conversationId}/reopen — sets status back to Open.

D — Token usage and billing

  • Token consumption is accumulated per subscription period in StoreAiTokenUsage.
  • GET /api/v1/store/subscription/usage returns aiInputTokensUsed, aiOutputTokensUsed alongside the plan's included quota.
  • Overage beyond IncludedInputTokensPerPeriod / IncludedOutputTokensPerPeriod is tracked as OverageInputTokens / OverageOutputTokens and billed at OverageInputTokenPriceIqd / OverageOutputTokenPriceIqd per 1,000 tokens.
  • Social Messaging is available on all subscription plans (AllowsSocialMessaging = true); token quota and overage pricing are configured per plan.

Source: DOCS/03-integrations/specs/store/flow.md