# SnapAct POS Integration Guide

This guide explains exactly how a partner, POS vendor, or store system connects to SnapAct Connect.

The goal is simple:

```text
Partner POS -> SnapAct Connect API -> Live SnapAct catalog and verified inventory
SnapAct orders -> Signed webhook -> Partner POS/order dashboard
```

SnapAct Connect is server-to-server. API keys must never be placed inside a browser, mobile app, cashier tablet frontend, or public website JavaScript.

## 1. Who Does What

| Person or system | Responsibility |
| --- | --- |
| Partner | Creates the SnapAct Connect API key in Partner Dashboard. |
| POS provider | Stores the API key securely on its backend or secure local connector. |
| SnapAct Connect API | Receives catalog, price, stock, and verification updates. |
| SnapAct buyer app | Shows live partner products, verified inventory, distance, delivery estimate, and checkout. |
| POS webhook endpoint | Receives SnapAct paid order and order status events. |

## 2. Connection Options

| POS setup | How it connects |
| --- | --- |
| Cloud POS | POS backend calls `https://connect.snapact.io` directly. This is the best setup. |
| Desktop POS with internet | A small POS connector service runs on the store computer and calls SnapAct from the backend/service layer. |
| Offline-first POS | POS queues updates locally, then syncs when internet returns. |
| POS without API support | Partner uses SnapAct Partner Dashboard catalog manually until the POS vendor supports API sync. |

## 3. Partner Setup

1. Partner opens Partner Dashboard.
2. Partner goes to `Connect API`.
3. Partner creates an API key with a clear label such as the POS provider name or store system name.
4. Partner copies the key once.
5. Partner gives the key to the POS provider through a secure setup channel.
6. POS provider stores the key in server secrets, environment variables, or an encrypted connector configuration.

Do not send the key through public chat screenshots, customer support screenshots, public issue trackers, or frontend code.

If a key is exposed:

1. Revoke the key in Partner Dashboard.
2. Create a new key.
3. Update the POS integration with the new key.
4. Delete the revoked key from the dashboard after cleanup if the partner wants a cleaner key list.

## 4. POS Product Mapping

The POS must map its own product fields into SnapAct fields.

| SnapAct field | Required | POS source |
| --- | --- | --- |
| `external_ref` | Yes | POS product ID, SKU, barcode, or stable inventory ID. |
| `title` | Yes | Product name in POS. |
| `description` | No | POS description, product notes, or short product detail. |
| `price_naira` | Yes | Current selling price in naira. |
| `currency` | No | Use `NGN` for Nigeria. |
| `category_key` | No | POS category code if available. |
| `category_label` | Yes | Buyer-friendly category name. |
| `availability` | Yes | `in_stock`, `low_stock`, `out_of_stock`, `on_request`, or `scheduled_only`. |
| `status` | Yes | `live` to show in SnapAct, `draft` to hide from buyers. |
| `stock_quantity` | Strongly recommended | Current stock count from POS. |
| `inventory_verified_at` | Strongly recommended | Time the POS or shelf snap last confirmed stock. |
| `inventory_verification_source` | Strongly recommended | `pos_sync`, `merchant_shelf_snap`, or another source label. |
| `image_url` | Optional | Product image hosted by POS or partner system. |
| `discount_percent` | Optional | Current discount percentage. |
| `compare_at_price_naira` | Optional | Original crossed-out price if discounted. |

Important rule:

```text
external_ref must stay stable.
```

If the POS changes `external_ref`, SnapAct treats the product as a different item.

## 5. Full Catalog Sync

The POS should run a full catalog sync:

| When | Reason |
| --- | --- |
| First connection | Load all live POS products into SnapAct. |
| Once daily | Repair missed updates and keep catalog accurate. |
| After POS migration | Rebuild SnapAct catalog from the new POS source. |
| After long offline period | Reconcile stock, price, and availability. |

Endpoint:

```text
POST https://connect.snapact.io/connect/v1/catalog/items
```

Headers:

```text
Authorization: Bearer $SNAPACT_CONNECT_API_KEY
Content-Type: application/json
```

Payload shape:

```json
{
  "items": [
    {
      "external_ref": "$POS_PRODUCT_ID",
      "title": "$POS_PRODUCT_NAME",
      "description": "$POS_PRODUCT_DESCRIPTION",
      "price_naira": "$POS_PRICE_NAIRA",
      "currency": "NGN",
      "category_label": "$POS_CATEGORY_LABEL",
      "availability": "$POS_AVAILABILITY",
      "stock_quantity": "$POS_STOCK_QUANTITY",
      "inventory_verified_at": "$POS_STOCK_VERIFIED_AT",
      "inventory_verification_source": "pos_sync",
      "status": "live"
    }
  ]
}
```

## 6. Incremental POS Updates

The POS should sync immediately when any of these happen:

| POS event | SnapAct update |
| --- | --- |
| Product created | Send new item with `status: "live"` or `status: "draft"`. |
| Product name changed | Send same `external_ref` with updated `title`. |
| Price changed | Send same `external_ref` with updated `price_naira`. |
| Stock changed after sale | Send same `external_ref` with updated `stock_quantity`. |
| Restock received | Send updated `stock_quantity` and `inventory_verified_at`. |
| Product hidden in POS | Send same `external_ref` with `status: "draft"`. |
| Product discontinued | Delete the item from SnapAct or set `status: "draft"`. |

The best POS behavior is:

```text
Every POS sale, restock, price change, or product edit triggers a SnapAct sync.
```

## 7. Delete or Hide Products

Use delete when the item should no longer exist in SnapAct.

Endpoint:

```text
DELETE https://connect.snapact.io/connect/v1/catalog/items
```

Payload:

```json
{
  "external_ref": "$POS_PRODUCT_ID"
}
```

Use `status: "draft"` instead of delete when the product may return later.

## 8. Verified Inventory API

The Verified Local Inventory API is used by external systems that need to know what is available near a buyer location.

Endpoint:

```text
GET https://connect.snapact.io/inventory/nearby?lat=$BUYER_LAT&lng=$BUYER_LNG&product=$PRODUCT_SEARCH&radius_km=$RADIUS_KM
```

It returns:

| Field | Meaning |
| --- | --- |
| `partner_id` | SnapAct partner profile ID. |
| `trading_name` | Partner display name. |
| `city`, `state_region`, `country_name` | Buyer-safe store location. |
| `distance_km` | Distance from buyer delivery point to partner. |
| `delivery_estimate_minutes` | Estimated delivery time. |
| `rating` | Partner buyer rating data. |
| `logo_url`, `background_url` | Partner display images. |
| `buy_now_url` | Web purchase/deep link. |
| `app_url` | SnapAct app deep link. |
| `verified_inventory.quantity` | Verified stock count. |
| `verified_inventory.last_snap_at` | Last verified stock time. |
| `verified_inventory.source` | Verification source. |
| `item` | Product details. |

For a product to appear here, it must have:

```text
stock_quantity greater than 0
inventory_verified_at within the verified inventory window
status set to live
availability not set to out_of_stock
```

## 9. SnapAct Orders Back Into POS

If the POS provider wants SnapAct orders inside the POS, the partner must add a webhook in Partner Dashboard.

Partner Dashboard -> Connect API -> Webhooks

Webhook URL:

```text
https://{pos-provider-domain}/snapact/webhook
```

Supported events:

| Event | What POS should do |
| --- | --- |
| `order.paid` | Create a new paid SnapAct order in POS. |
| `order.status.updated` | Update order fulfillment status in POS. |
| `catalog.item.upserted` | Log or reconcile product updates. |
| `catalog.item.deleted` | Log or reconcile product deletion. |

SnapAct signs every webhook delivery.

Webhook headers:

| Header | Meaning |
| --- | --- |
| `X-SnapAct-Event-Id` | Unique event ID. |
| `X-SnapAct-Event-Type` | Event name. |
| `X-SnapAct-Timestamp` | Unix timestamp. |
| `X-SnapAct-Signature` | HMAC signature. |

The POS webhook server must verify the signature before trusting the payload.

## 10. SDK Installation

For Node.js POS backends, install the SDK:

```bash
npm install @snapact/connect-sdk
```

Use it server-side only:

```js
import { SnapActConnect } from "@snapact/connect-sdk";

const snapact = new SnapActConnect({
  apiKey: process.env.SNAPACT_CONNECT_API_KEY
});

await snapact.upsertCatalogItems({
  external_ref: process.env.POS_PRODUCT_ID,
  title: process.env.POS_PRODUCT_NAME,
  price_naira: Number(process.env.POS_PRICE_NAIRA),
  category_label: process.env.POS_CATEGORY_LABEL,
  availability: process.env.POS_AVAILABILITY,
  status: "live",
  stock_quantity: Number(process.env.POS_STOCK_QUANTITY || 0),
  inventory_verified_at: new Date().toISOString(),
  inventory_verification_source: "pos_sync"
});
```

Webhook signature verification:

```js
import { verifyWebhookSignature } from "@snapact/connect-sdk";

const valid = await verifyWebhookSignature({
  secret: process.env.SNAPACT_WEBHOOK_SECRET,
  rawBody,
  timestamp: request.headers["x-snapact-timestamp"],
  signature: request.headers["x-snapact-signature"]
});

if (!valid) {
  throw new Error("Invalid SnapAct webhook signature");
}
```

## 11. POS Sync Timing

Recommended timing:

| Sync type | Timing |
| --- | --- |
| Product create/update | Immediately. |
| Price update | Immediately. |
| Sale stock reduction | Immediately or within 60 seconds. |
| Restock | Immediately. |
| Full catalog reconciliation | Once daily. |
| Offline queue retry | Every 1-5 minutes until successful. |

## 12. Error Handling

| API error | Meaning | POS action |
| --- | --- | --- |
| `INVALID_API_KEY` | Key missing, revoked, malformed, or wrong. | Ask partner to create a new Connect key. |
| `INSUFFICIENT_SCOPE` | Key does not have permission for that action. | Create a key with the needed scope. |
| `PARTNER_NOT_APPROVED` | Partner account is not approved/live. | Partner must complete approval. |
| `INVALID_TITLE` | Product title missing. | Send a valid POS product name. |
| `MISSING_EXTERNAL_REF` | Stable POS product ID missing. | Send product SKU/barcode/POS ID. |
| `INVALID_PRICE` | Price is missing or invalid. | Send positive `price_naira`. |
| `INVALID_AVAILABILITY` | Availability value is not supported. | Use allowed availability values. |
| `INVALID_STATUS` | Status value is not supported. | Use `live` or `draft`. |

## 13. What To Tell A POS Provider

Send this to the POS vendor:

```text
We want our POS to sync products to SnapAct Connect.

Base URL:
https://connect.snapact.io

Authentication:
Authorization: Bearer {partner_connect_api_key}

Required integration:
1. Full product sync to POST /connect/v1/catalog/items.
2. Incremental sync whenever product, price, stock, or availability changes.
3. Use the POS product ID/SKU/barcode as external_ref.
4. Send stock_quantity and inventory_verified_at so SnapAct can show verified local inventory.
5. Optional: Add a webhook endpoint to receive order.paid and order.status.updated events from SnapAct.
6. Store the API key server-side only.
7. Never expose the API key in browser, mobile app, or cashier frontend JavaScript.
```

## 14. Production Checklist

Before a partner goes live with POS sync:

| Check | Required |
| --- | --- |
| Partner is approved in SnapAct | Yes |
| Connect API key created | Yes |
| API key stored server-side only | Yes |
| Full catalog sync tested | Yes |
| Incremental stock update tested | Yes |
| Price update tested | Yes |
| Product hide/delete tested | Yes |
| Verified inventory lookup tested | Yes |
| Webhook signature verification tested | Required if using webhooks |
| Failed sync retry queue exists | Strongly recommended |
| Partner knows how to revoke key | Yes |

## 15. Simple Mental Model

```text
POS product ID becomes SnapAct external_ref.
POS stock count becomes SnapAct stock_quantity.
POS latest stock time becomes SnapAct inventory_verified_at.
POS price becomes SnapAct final buyer item price.
SnapAct adds service fee separately at checkout.
SnapAct sends paid order events back to POS through signed webhooks.
```

