Turn your Wix store events into automated AI phone calls — COD confirmation, abandoned cart recovery, order confirmation, delivery updates, win-back and instant lead callback — with every call's outcome written back onto the Wix order or contact.
Wix Voice AI is a free, open-source Wix app that connects your Wix eCommerce / Wix Stores site to Telenow, an AI voice-agent service, so an AI voice agent automatically calls your shoppers at the moments that matter — order confirmation calls, COD confirmation to cut RTO (return-to-origin), abandoned cart recovery, delivery updates, win-back re-engagement, and instant lead callback (speed-to-lead) — in a natural, multilingual voice including Hindi and other Indian and global languages. Each store event (new order, fulfillment, new contact, abandoned checkout) becomes a real phone conversation, and the call outcome is written straight back onto the Wix order (timeline activity) or contact (label + note) so nothing lives in a spreadsheet. The app itself is free; it connects to Telenow with your own account and vai_live_… API key, and Telenow bills for call usage on its own platform (no charges go through Wix). Bring your existing Telenow agents, toggle the automations you want, and your store starts "talking to" your customers.
Free app — requires a Telenow account. The app is free to install. Telenow is a separate third-party voice-AI service that bills for call usage on its own platform; you connect it with your own
vai_live_…API key (Telenow → Developers → API Keys). No charges go through Wix.
- Features
- Installation
- Configuration
- How it works
- Wix Dev Center setup
- OAuth / install flow
- Webhooks (inbound from Wix)
- Automations & the data written back
- Telenow webhooks (inbound from Telenow)
- Security notes
- Production checklist
- Local round-trip test
- Project layout
- About Telenow
- License
Install once and Telenow places automated AI voice calls to your shoppers on store events, then writes the result back onto the Wix order or contact. Every automation is independently toggleable, with its own agent, call delay, quiet-hours window and filters.
- 📞 Order confirmation calls — a warm thank-you / confirm-details call goes out the moment an order is placed (eCommerce Order Created), reducing address errors and support tickets. Outcome logged as an order activity.
- 💰 COD confirmation & RTO reduction — every cash-on-delivery / pay-offline order gets a confirmation call before you fulfill, so you ship orders the customer actually wants. The result labels the contact
telenow-cod-confirmed/telenow-cod-cancelled/telenow-cod-no-responseand adds an order activity. (COD-cancelled does not auto-cancel the order — it only flags it for your review.) - 🛒 Abandoned cart recovery — when a checkout is abandoned (eCommerce Abandoned Checkout), Telenow can call the shopper with a friendly nudge, the recovery URL and an optional discount to bring the sale back (best-effort / plan-dependent — see below).
- 🚚 Delivery & fulfillment updates — on Fulfillments Updated, the AI agent reads out the tracking number / URL and carrier so "where is my order?" gets answered before it becomes a complaint. Delivered / failed-delivery (NDR retry) are stubs you wire to your carrier/3PL.
- ⚡ Instant lead callback (speed-to-lead) — every new Wix CRM Contact Created (site signup, lead form, contact box) triggers an instant AI callback while interest is high. New leads appear in the Recent leads view in the settings page with the call result and a contact note.
- 🔁 Win-back / re-engagement — a scheduled sweep finds customers whose most recent order is older than N days and calls them back, with an optional discount code.
- ✍️ Outcome write-back — results land where you work: an order activity (timeline note), a contact label for COD outcomes, and a contact note for lead callbacks.
- 🌍 Natural multilingual voice — Telenow speaks in a natural AI voice across English, Hindi and other Indian languages plus global languages, and can look up an order, product or customer live during the call, so the conversation is genuinely two-way.
- 🎛️ Per-automation controls — independent on/off toggle, Telenow agent ID, call delay, timezone-aware quiet-hours window, and per-store filters (e.g. extra COD payment-method matchers, minimum order value, discount code).
- 🔒 Secure by design — RS256-JWT verification of Wix webhooks, HMAC-SHA256 verification of Telenow result webhooks, signed session-token auth for the settings API, E.164 phone normalization, secret/PII masking in logs, stable-id dedupe, and lead-PII purge on uninstall.
git clone https://github.com/TelenowAI/wix-voice-ai.git
cd wix-voice-ai
npm install
cp .env.example .env # fill in the values below
npm start # or: npm run dev (node --watch)You need a public HTTPS URL (Wix and Telenow both call you). In dev use a tunnel:
ngrok http 3000 # then set HOST=https://<id>.ngrok-free.app in .envRequirements: Node.js 18+ (uses the global fetch), a Wix Dev Center app, and a Telenow account with a vai_live_… API key.
Copy .env.example to .env and fill in the values:
| Var | Required | Description |
|---|---|---|
HOST |
✅ | Public HTTPS base URL of this app (no trailing slash). Builds the OAuth redirect URL and the Telenow webhook target. |
PORT |
Listen port (default 3000). |
|
WIX_APP_ID / WIX_APP_SECRET |
✅ | From your app in the Wix Dev Center → your app → OAuth (App ID + App Secret Key). The secret also signs the settings-UI session token. |
WIX_PUBLIC_KEY |
✅ (for webhooks) | Your app's webhook public key (PEM), from the Wix Dev Center → your app → Webhooks (or View ID & Keys). Wraps multi-line — wrap in quotes and use \n, e.g. "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----". If missing, the app boots but webhook verification is disabled (loud warning) and inbound webhooks are rejected with 503 so you can finish OAuth first. |
TELENOW_API_BASE |
Telenow API base (default https://api.telenow.ai). |
|
DATA_DIR |
Where the file store persists (default ./data). |
|
DEFAULT_PHONE_COUNTRY |
ISO-2 country for E.164 normalization of local numbers (default IN). |
|
SWEEP_INTERVAL_MS / SWEEP_RUN_ON_BOOT |
Win-back scheduler cadence (default 6h) / run the sweep once at boot for testing. |
The Telenow API key is not an env var — each merchant pastes their own
vai_live_…key in the settings page (/app), stored per instance.
Wix site ──webhook(signed JWT)──▶ this app ──POST /api/sessions/initiate-call──▶ Telenow
▲ │ ▲ │
└──REST API (order/contact)────────┘ └──────POST /telenow/webhook (HMAC) ◀──────────┘
(call.ended / call.analyzed)
A Wix store event arrives as a signed JWT webhook; the app verifies it, builds the call context, and asks Telenow to place an AI voice call. When the call ends, Telenow posts the result (with disposition / transcript) back to the app, which verifies the HMAC signature and writes the outcome onto the originating Wix order or contact.
Tech: Node 18+ (global fetch), Express, jsonwebtoken (to verify Wix's RS256 webhook JWTs). No DB — a file-based store stub you swap in production.
- Create the app at dev.wix.com → Create New App. Copy the App ID and App Secret Key into
WIX_APP_ID/WIX_APP_SECRET. - OAuth → Redirect URL: set it to
HOST/wix/oauth/callback. Set the App URL (what opens after install) toHOST/app(Wix appends context; the page also accepts?instanceId=). - Permissions: request Wix Stores / eCommerce — Read & Manage Orders and Wix CRM — Read & Manage Contacts (read orders/contacts, add order activities, add contact labels/notes).
- Webhooks → Create Webhook and point the Callback URL at
HOST/webhooks/wixfor each event:- eCommerce → Order Created (order confirmation + COD)
- eCommerce → Order Approved (optional — fires when an online payment is received or a COD order is auto-approved)
- eCommerce → Fulfillments Updated (delivery / tracking)
- CRM → Contact Created (lead callback)
- eCommerce → Abandoned Checkout (optional / plan-dependent — abandoned cart)
- App → App Removed (so the app purges all data, including lead PII, on uninstall)
- Public key: copy the webhook public key shown on the Webhooks page (or View ID & Keys) into
WIX_PUBLIC_KEY. This is what the app uses to verify every inbound webhook (RS256).
- The merchant adds the app from the Wix App Market. Wix redirects to
HOST/wix/oauth/callback?code=…. - The app exchanges the
codeatPOST https://www.wixapis.com/oauth/access(grant_type:"authorization_code") → a long-lived refresh_token (+ a short-lived access_token). It persists the refresh_token per instance and captures the instanceId viaGET https://www.wixapis.com/apps/v1/instance. - Access tokens are minted on demand from the refresh token (
grant_type:"refresh_token") and cached in memory. All Wix REST calls sendAuthorization: <access_token>(Wix uses the raw token, notBearer). - The app mints a short-lived signed session token for the now-verified instance and redirects the merchant to
/app#t=…(the token rides in the URL fragment, never the query string or logs). There they paste their Telenow API key (validated viaGET /api/v1/me), pick an agent ID per automation, toggle automations, and set delays/quiet-hours. Saving a new key (re)subscribes the Telenow result webhook viaPOST /api/v1/hooks.
Wix delivers webhooks as a signed JWT in the raw request body, signed with your app's RSA key pair. We verify and decode it exactly as Wix documents:
const rawPayload = jwt.verify(body, WIX_PUBLIC_KEY, { algorithms: ['RS256'] });
const event = JSON.parse(rawPayload.data); // { eventType, instanceId, data }
const eventData = JSON.parse(event.data); // the entity eventWe dispatch on event.eventType (wix.ecom.v1.order_created, wix.ecom.v1.order_approved, wix.ecom.v1.fulfillments_updated, wix.contacts.v4.contact_created, wix.ecom.v1.checkout_abandoned, wix.app_market.v1.app_removed) and extract the affected entity from createdEvent.entity / updatedEvent.entity / actionEvent.body. We ACK Wix with 200 immediately and process automations in the background so retries never double up.
A Wix order is treated as Cash-on-Delivery when its paymentStatus is unpaid (NOT_PAID / PENDING) and an offline/manual payment method is present, or when the payment method itself matches a COD/offline matcher. Add extra matcher substrings per-store via the COD confirmation automation's filters.codMethodsExtra.
A CRM Contact Created is the lead signal: name from info.name.first/last, email from info.emails.items[], phone from info.phones.items[] (normalized to E.164). The lead is stored first (so it shows in Recent leads even if the call is skipped), then an instant callback is placed, then the row is patched with the result.
Each automation builds a variables object and calls POST /api/sessions/initiate-call (note: not /api/v1) with the agent, the E.164 number, an identifier (order:<id> / lead:<id> / cart:<id> / customer:<id>), and machineDetection:"hangup". The response is enveloped — we read data.sessionId. We persist sessionId → entity so the result webhook can find the order/lead.
| Automation | Wix trigger | Variables passed to the agent | Write-back on result |
|---|---|---|---|
| Order confirmation | Order Created | customer_name, order_number, order_items, order_total, payment_status |
order activity |
| COD confirmation | Order Created (COD only) | customer_name, order_number, order_items, order_total, payment_method, shipping_city |
contact label telenow-cod-confirmed / -cancelled / -no-response + order activity |
| Fulfillment / delivery | Fulfillments Updated | customer_name, order_number, tracking_number, tracking_url, carrier |
order activity |
| Lead callback | Contact Created | customer_name, email, source, labels |
lead row updated + contact note |
| Win-back | scheduled | customer_name, days_since_last_order, discount_code? |
logged / activity when an order id is known |
| Abandoned cart | Abandoned Checkout (best-effort) | customer_name, cart_items, cart_total, recovery_url, discount_code? |
logged (no order yet) |
COD cancelled does NOT auto-cancel the order — we only label + note it so the merchant reviews first. There's a clearly-marked
TODOinsrc/webhooks/telenow.jsto optionally cancel via the eCommerce Orders API.
We subscribe with POST /api/v1/hooks (events: ["call.ended","call.analyzed"], source: "wix", includeTranscript: true) and store the returned signing secret per instance. Telenow then POSTs results to HOST/telenow/webhook with:
X-VoiceAI-Signature: sha256=<hex HMAC-SHA256 of the raw body>
X-VoiceAI-Event: call.ended | call.analyzed
X-VoiceAI-Delivery: <uuid>
We verify by recomputing the HMAC over the raw body with that secret (constant-time compare; hex canonical, base64 fallback), resolve the entity via the persisted sessionId (falling back to the identifier), and — lead branch first — patch the lead row, else write back to the order. COD confirmed-vs-cancelled is read from analysis.disposition (with a summary/transcript keyword fallback).
- Three verification schemes. Inbound Wix webhooks are verified as an RS256 JWT against
WIX_PUBLIC_KEY; inbound Telenow webhooks are verified as a hex HMAC-SHA256 against the per-hook signing secret; the settings API (/api/*) is authenticated with a signed session token (HMAC-SHA256 over<instanceId>.<exp>, keyed byWIX_APP_SECRET), minted only at the verified OAuth entry point and sent asAuthorization: Bearer <t>. The non-secret?instanceId=query is never trusted to authenticate data routes (that would be an IDOR exposing another tenant's leads/key). Both webhook routes receive the raw body (mounted before the JSON parser) so the bytes/signature match exactly. Bad signatures get401. - E.164 normalization. Phone numbers from Wix orders (
billingInfo.contactDetails.phone) and contacts (info.phones.items[]) are normalized to E.164 before dialing (src/util/phone.js); un-normalizable numbers are skipped, never dialed. - Never log the API key or full phone. The Telenow
X-API-Keyand Wix tokens are never logged or sent to the browser — the settings page only ever sees a masked key hint. Phone numbers are masked in logs and in the leads table. - Dedupe on stable Wix ids. We dedupe per
(automation, instance, STABLE entity id)— order id / contact id — not a freshly-generated local id, so webhook redeliveries never double-call. The mark is released if placement fails so a genuine retry can go through. - Lead PII is purged on uninstall. Lead rows hold name/email/phone; the App Removed webhook calls
deleteInstance(), which purges instances, settings, hooks, call map, dedupe attempts and leads. - Quiet hours. Calls are suppressed inside each automation's local quiet-hours window (re-checked at fire time for delayed calls).
- Swap the file store for a real DB.
src/store.jsis an in-memory + JSON-file stub (no locking, last-write-wins, single-process). Move instances/settings/callMap/hooks/attempts/leads to Postgres/MySQL/DynamoDB. The logical stores are documented at the top of the file. - Durable scheduling for delays + sweeps. Delayed calls use
setTimeoutand win-back usessetInterval— neither survives a restart or scales across instances. Use a job queue (BullMQ/Redis, SQS) ornode-cronwith a leader lock. (Marked TODO in_base.jsandwinBack.js.) - Shared access-token cache.
src/wix.jscaches minted access tokens in process memory. For multi-instance prod, move it to a shared store (Redis) or mint per request. - Host on HTTPS with a stable
HOST. Update the OAuth redirect URL + webhook callback URLs in the Dev Center if it changes. - Confirm the abandoned-checkout webhook is available for the target site plans and verify the payload field names;
src/automations/abandonedCart.jsis wired best-effort behind a toggle. - Verify delivered / failed-delivery by wiring a carrier/3PL webhook (Shippo, AfterShip, Delhivery, Shiprocket…) — stubs in
src/automations/orderUpdates.js. - Replace the win-back last-order scan.
winBack.jspages the Orders search and keeps the most-recent order per buyer; for large stores maintain a truelast_order_atindex from the Order Created webhook. - Per-customer call frequency caps / suppression list (don't call the same shopper repeatedly across automations).
- Data-subject requests. Lead PII is purged on uninstall; for in-life GDPR/erasure also forward redaction to Telenow for the actual voice recordings/transcripts.
A self-contained harness proves the entire integration chain end-to-end on
your machine — no real Wix site, no real Telenow backend, and no hosting
required. It drives the app's real modules (the wired Express app, the Wix
RS256-JWT webhook verifier, placeCall, the Telenow client, the result-webhook
receiver and the lead store), with an in-process mock Telenow API.
npm run roundtripWhat it exercises (the lead-callback path, whose write-back is to the app's own lead store, so no Wix REST API is needed):
- Generates a throwaway RSA key pair, sets
WIX_PUBLIC_KEYto the public key, and seeds an installed instance + Telenow hook + settings directly viastore.js(bypassing OAuth), enabling theleadCallbackautomation with an agent id and a Telenow API key. - POSTs a real RS256-signed JWT to
/webhooks/wix(signed with the matching private key), shaped as Wix delivers it — the double-encoded envelope for awix.contacts.v4.contact_createdevent with a contact phone — which the app's ownjwt.verifyaccepts. - Asserts the mock Telenow received an
initiate-callwith the expected E.164 number and alead:<id>identifier, and that a lead row was stored, linked to the Wix contact id, and moved toplaced. - Fires a
call.analyzedresult webhook back at/telenow/webhook, signed with the mock's hook secret (X-VoiceAI-Signature: sha256=<hex>), and asserts the lead is updated tocompleted/ dispositionconfirmed. - Asserts a result webhook with a wrong signature is rejected with
401and leaves the lead unchanged.
One step is not exercised locally: when the result is resolved via the
session→call map, the lead branch makes a best-effort Wix REST call
(addContactNote) to note the outcome on the underlying contact. To keep the
harness fully offline, the result webhook is resolved via the lead:<id>
identifier instead (same signature check + same updateLead write-back, zero
outbound calls), so that contact-note write-back to the live Wix API is the only
part of the chain not driven here. Everything else runs end-to-end in-process.
It prints PASS/FAIL per check and exits non-zero on any failure. It uses a
throwaway temp DATA_DIR (removed on exit) and dummy credentials, so it needs no
real keys and touches no network. Test files live in test/
(test/mock-telenow.mjs, test/roundtrip.mjs).
src/
server.js Express app: routers, body parsers, settings API, scheduler
wix.js Wix OAuth + token refresh + REST client + instance lookup + write-back
auth.js OAuth /wix/oauth/callback; persists refresh token, subscribes Telenow hook
session.js Signed session tokens (HMAC) for the settings-UI /api/* auth
telenow.js Telenow API client (me, initiateCall, createHook, listHooks, deleteHook)
settings.js Per-instance settings model + defaults + redaction
store.js Persistence STUB (file JSON) — instances/settings/callMap/hooks/attempts/leads
webhooks/
wix.js Wix webhook receiver (RS256 JWT) + dispatch + app-removed cleanup
telenow.js Telenow result receiver (hex HMAC) + write-back (lead branch first) + hook lifecycle
automations/
_base.js placeCall(): gating (enabled/key/agent/quiet/delay) + stable-id dedupe + dial + map
orderConfirmation.js codConfirmation.js orderUpdates.js leadCallback.js winBack.js abandonedCart.js
util/
phone.js E.164 normalization (Wix order/contact shapes)
quietHours.js timezone-aware quiet-hours check
public/app.html embedded settings UI + Recent-leads card + legal footer
Telenow is an AI voice-agent platform that places and answers natural, multilingual phone calls — English, Hindi and other Indian and global languages — for eCommerce and customer operations. Build an agent once, then drive it from your store events to confirm orders, cut COD returns, recover carts, send delivery updates, and call back leads, with full transcripts and call analysis. Learn more at telenow.ai, read the documentation, or see pricing.
Released under the MIT License.