POS Integrations
SalesArck currently supports Square and Clover.
Both providers follow the same shape:
- OAuth helpers build and sign the authorization state.
- The OAuth callback stores encrypted tokens in
pos_connections. - Webhook payloads are stored in
pos_webhook_events_raw. - The ingestion pipeline normalizes provider data into
NormalizedTransaction. - Wallet credit happens through the shared ingestion core.
Square
OAuth
Merchant connect starts from POST /api/v1/client/tenants/:tenantId/pos-connections/square/connect.
The callback at GET /api/v1/oauth/square/callback:
- verifies the signed state payload
- exchanges the code for tokens
- encrypts access and refresh tokens at rest
- upserts the
pos_connectionsrow for(tenant_id, pos_type='square') - redirects back to the frontend
Webhooks
POST /api/v1/webhooks/square:
- Verifies the Square signature when a webhook key is configured.
- Resolves the tenant by
merchant_idand the active Square connection. - Stores the raw payload.
- Inserts a
webhook_jobsrow and returns200. - The same process then ingests that job, which calls
processSquareWebhook(), credits points, and sends the email.POST /api/v1/internal/cron/process-pending-webhooksretries jobs that did not finish.
Poll fallback
POST /api/v1/internal/cron/square-poll-payments polls Square ListPayments for every active Square connection.
The poll path:
- refreshes the access token when needed
- paginates up to 25 pages per connection
- reuses the same normalization and ingestion core as the webhook path
- updates
square_payments_watermark_atandlast_synced_at
Clover
OAuth
Merchant connect starts from POST /api/v1/client/tenants/:tenantId/pos-connections/clover/connect.
The callback at GET /api/v1/oauth/clover/callback:
- verifies the signed state payload
- exchanges the code for tokens
- encrypts the access token at rest
- stores a null refresh token, because Clover does not provide one
- upserts the
pos_connectionsrow for(tenant_id, pos_type='clover') - redirects back to the frontend
Webhooks
POST /api/v1/webhooks/clover:
- Handles Clover's verification handshake payload.
- Verifies the webhook signature for real notification events.
- Rejects payloads that contain multiple merchant IDs.
- Resolves the tenant by merchant ID and active Clover connection.
- Stores the raw payload.
- Inserts a
webhook_jobsrow and returns200. - The same process then ingests that job, which calls
processCloverWebhook(), credits points, and sends the email.POST /api/v1/internal/cron/process-pending-webhooksretries jobs that did not finish.
Clover webhooks are notification-only. The processor must fetch each payment from Clover's REST API before normalization.
Recovery
POST /api/v1/internal/cron/clover-poll-payments lists Clover payments modified since clover_payments_watermark_at for every active Clover connection and runs them through the same idempotent ingestion path as webhooks.
A payment that is still settling holds the watermark so the next poll fetches it again. A future-dated timestamp cannot move the watermark past now.
Failed webhook jobs are retried by POST /api/v1/internal/cron/process-pending-webhooks, which claims due webhook_jobs rows. The webhook handler already runs the same job as soon as it is stored. Square and Clover polls stay the fallback when a webhook never arrives.
Adapters
Square adapter
normalizeSquarePaymentRecord() accepts webhook or REST payment shapes and keeps only completed payments.
It extracts:
- provider transaction ID
- amount in minor units
- currency
- transaction timestamp
- optional contact data
Clover adapter
normalizeCloverPayment() accepts a fetched Clover payment record and keeps only payments eligible for loyalty credit.
It extracts:
- provider transaction ID
- amount in minor units
- currency
- transaction timestamp
- optional contact data from the payment or embedded customer records
Connection Semantics
pos_connections enforces one row per (tenant_id, pos_type).
That means reconnecting updates the same row in place, and disconnecting only flips status to disconnected.
Environment Variables
Square
SQUARE_APP_IDSQUARE_APP_SECRETSQUARE_ENVIRONMENTSQUARE_WEBHOOK_SIGNATURE_KEY
Clover
CLOVER_APP_IDCLOVER_APP_SECRETCLOVER_ENVIRONMENTCLOVER_WEBHOOK_SECRET
Shared
ENCRYPTION_KEYAPI_BASE_URLFRONTEND_URL