Skip to main content
Ratings: ZIPCushions Google

4.9/5

ZIPCushions Etsy

5/5

|
Flat 15% OFF Sitewide, Use Code: ZIPCUSHION15
|
Flat 15% OFF Sitewide, Use Code: ZIPCUSHION15
  • Custom Cushion Experts
    Custom Cushion Experts
  • Any Size Cushions
    Any Size
  • Any Color Custom Cusions
    Any Color
  • Any Shape Custom Cusions
    Any Shape
  • More Than 50000 happy customers
    Loved by 50,000+ Homes

BLOGS

Read Our Latest Blog Posts

How to Measure for Custom Cushions: The Step-by-Step Guide That Prevents Costly Mistakes
How to Measure for Custom Curtains: Windows, Patios, Pergolas & More
Outdoor Cushion Buying Guide 2026: How to Choose Cushions That Survive Sun, Rain & Wind
Why Sunbrella is The Best Choice for Patio Cushions, Curtains & Throws: The Complete Spring Guide
Level Up Your Netflix Binges: Christmas Ready Custom Couch Cushions
From Cozy to Luxe: ZIPCushions' Cyber Monday Bestsellers Revealed
ZIPCushions Prive Makes Its Grand Debut at BDNY 2025
Black Friday Cushion Deals: Your Home's Softest Upgrade
Thanksgiving Hosting Essentials: From Table Decor to Cozy Seating
ZIPCushions at BDNY 2025: Unveiling Prive Collection & Exclusive Design Solutions
Cushions Demystified: What Makes Outdoor vs Indoor Unique?
Easy Room Makeover Ideas: 7 Best Cushion Combinations
Marine Boat Cushions: Complete Buyer's Guide for Ultimate Water Comfort
Tiny Patio Makeover Ideas: 5 Secrets Interior Designers Don't Want You to Know
Before & After: Transform Your Entryway with Custom Mudroom Cushions
How to Clean Couch Cushions: The Complete Guide for Every Fabric Type
How Often Should You Replace Sofa Cushions?
Beyond Comfort: How Cushion Density Affects Your Health
How to Fix Sagging Couch Cushions: Expert Tips for Lasting Comfort
How-to-Design-the-Perfect-Summer-Reading-Nook-with-ZIPCushions | ZIPCushions
Father-s-Day-Gift-Guide-For-the-Man-Who-Has-Everything | ZIPCushions
5-Backyard-BBQ-Setups-that-Sizzle-with-Comfort | ZIPCushions
Summer-2025-Style-Guide-Upgrade-Your-Outdoors-from-Memorial-Day-to-Monsoon-Season | ZIPCushions
Best-Fabrics-for-Indoor-Outdoor-Cushions-The-Ultimate-Guide | ZIPCushions
: * * * ARCHITECTURE NOTE — why this is one-shot install: * The JS sends BOTH: * - pubsub_token: best-guess parsed source_id from cookie * - cookie_raw: the raw cookie value * The server tries to extract source_id from `pubsub_token` first; if that * fails (e.g. Chatwoot changes cookie format in a future version) it falls * back to decoding `cookie_raw` server-side. This means future Chatwoot * upgrades won't require theme re-uploads — just bot_server updates. */ (function () { 'use strict'; // ========================================================================= // CONFIG — replace with your bot_server's public URL // ========================================================================= var FLASK_SYNC_URL = 'https://monitors-truly-designer-postings.trycloudflare.com/cart-tracker/sync'; var DEBOUNCE_MS = 600; // ========================================================================= // HELPERS // ========================================================================= function getCookie(name) { var m = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); return m ? decodeURIComponent(m[3]) : null; } /** * Extract source_id from a Chatwoot cw_conversation cookie value. * Tries every known format. Returns null if none work — the server-side * fallback (cookie_raw) will handle it instead. */ function extractSourceId(raw) { if (!raw) return null; // Format 1: JWT with payload {source_id, inbox_id, exp, iat} if (raw.split('.').length === 3) { try { var payload = raw.split('.')[1]; // base64url -> base64 payload = payload.replace(/-/g, '+').replace(/_/g, '/'); while (payload.length % 4) payload += '='; var decoded = JSON.parse(atob(payload)); if (decoded.source_id) return decoded.source_id; if (decoded.pubsub_token) return decoded.pubsub_token; } catch (e) { /* fall through */ } } // Format 2: JSON object if (raw.charAt(0) === '{') { try { var parsed = JSON.parse(raw); return parsed.source_id || parsed.pubsub_token || parsed.token || null; } catch (e) { /* fall through */ } } // Format 3: looks like a plain UUID if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw)) { return raw; } return null; } function getChatwootIdentifier() { return (window.$chatwoot && window.$chatwoot.identifier) || null; } function getChatwootEmail() { return (window.$chatwoot && window.$chatwoot.user && window.$chatwoot.user.email) || null; } function fetchCart() { return fetch('/cart.js', { credentials: 'same-origin', headers: { 'Accept': 'application/json' }, }).then(function (r) { if (!r.ok) throw new Error('cart fetch failed: ' + r.status); return r.json(); }); } function pushSnapshot(reason) { var rawCookie = getCookie('cw_conversation'); if (!rawCookie) return; // Chatwoot widget hasn't set its cookie yet var sourceId = extractSourceId(rawCookie); fetchCart().then(function (cart) { var payload = { // Best-guess client-side parsed token. Server prefers this. pubsub_token: sourceId, // Raw cookie as fallback. If pubsub_token is null OR the server's // cart→conversation matching fails, the server can decode this itself. cookie_raw: rawCookie, identifier: getChatwootIdentifier(), email: getChatwootEmail(), cart: cart, shop_domain: window.location.host, reason: reason, client_version: 'v2', }; if (reason === 'unload' && navigator.sendBeacon) { var blob = new Blob([JSON.stringify(payload)], { type: 'application/json' }); navigator.sendBeacon(FLASK_SYNC_URL, blob); } else { fetch(FLASK_SYNC_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), keepalive: true, }).catch(function () { /* silent — never break shopper UX */ }); } }).catch(function () { /* silent */ }); } var debounceTimer = null; function pushDebounced(reason) { if (debounceTimer) clearTimeout(debounceTimer); debounceTimer = setTimeout(function () { pushSnapshot(reason); }, DEBOUNCE_MS); } // ========================================================================= // TRIGGER 1: Widget ready — initial sync // ========================================================================= window.addEventListener('chatwoot:ready', function () { pushSnapshot('widget_ready'); }); if (window.$chatwoot && window.$chatwoot.hasLoaded) { setTimeout(function () { pushSnapshot('widget_already_ready'); }, 500); } // ========================================================================= // TRIGGER 2: Cart mutations — intercept fetch + XHR to Shopify cart endpoints // ========================================================================= var CART_MUTATION_PATHS = [ '/cart/add', '/cart/update', '/cart/change', '/cart/clear', '/cart/add.js', '/cart/update.js', '/cart/change.js', '/cart/clear.js', ]; function isCartMutation(url) { if (!url) return false; var s = typeof url === 'string' ? url : (url.url || ''); for (var i = 0; i < CART_MUTATION_PATHS.length; i++) { if (s.indexOf(CART_MUTATION_PATHS[i]) !== -1) return true; } return false; } if (window.fetch) { var origFetch = window.fetch; window.fetch = function () { var urlArg = arguments[0]; var p = origFetch.apply(this, arguments); if (isCartMutation(urlArg)) { p.then(function () { pushDebounced('cart_mutation_fetch'); }).catch(function () {}); } return p; }; } if (window.XMLHttpRequest) { var OrigXHR = window.XMLHttpRequest; var origOpen = OrigXHR.prototype.open; var origSend = OrigXHR.prototype.send; OrigXHR.prototype.open = function (method, url) { this.__ccpCartMutation = isCartMutation(url); return origOpen.apply(this, arguments); }; OrigXHR.prototype.send = function () { var self = this; if (this.__ccpCartMutation) { this.addEventListener('loadend', function () { if (self.status >= 200 && self.status < 400) { pushDebounced('cart_mutation_xhr'); } }); } return origSend.apply(this, arguments); }; } // ========================================================================= // TRIGGER 3: Page unload — last-chance sync // ========================================================================= window.addEventListener('pagehide', function () { pushSnapshot('unload'); }); document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'hidden') { pushSnapshot('visibility_hidden'); } }); })();