@qredex/agent
v1.1.4
Published
Lightweight browser agent for Qredex intent capture and locking
Maintainers
Readme
span
@qredex/agent
Deterministic browser runtime for Qredex intent capture, locking, and PIT handoff.
Quick Start
1. Choose an Install Path
CDN / script tag
<!-- Add before </body> -->
<script src="https://cdn.qredex.com/agent/v1/qredex-agent.iife.min.js"></script>For local engineer E2E, dev CDN usage, and local Core sandbox setup, see docs/DEVELOPMENT.md.
Core package
npm install @qredex/agentFramework wrappers
npm install @qredex/react
npm install @qredex/vue
npm install @qredex/svelte
npm install @qredex/angularThe wrapper packages include @qredex/agent, so framework consumers only need the wrapper package.
2. Attribution Sequence
The agent never adds to cart, removes items, or clears the merchant cart. The storefront owns cart state, checkout, and order submission; the agent only captures intent, locks IIT to PIT, and exposes PIT for backend attribution.
3. Connect Cart And Checkout
Your storefront owns cart mutations and order creation. Qredex only needs the merchant-reported cart transition so it can lock IIT to PIT. After lock, the merchant reads that PIT and carries it with the normal order payload to the merchant backend or direct Qredex order ingestion path, where attribution is resolved.
const agent = window.QredexAgent;
async function addToCart(product) {
// [Merchant] Snapshot the current cart before your platform changes it.
const previousCount = cart.itemCount;
// [Merchant] Perform the real cart mutation in your own backend/storefront.
await api.post('/cart', product);
// [Qredex] Report the cart transition after the merchant cart is updated.
agent.handleCartChange({
itemCount: cart.itemCount,
previousCount,
});
}
async function removeFromCart(line) {
// [Merchant] Snapshot the current cart before your platform changes it.
const previousCount = cart.itemCount;
// [Merchant] Perform the real cart mutation in your own backend/storefront.
await api.delete(`/cart/${line.id}`);
// [Qredex] Report the new live cart state so attribution can stay in sync.
agent.handleCartChange({
itemCount: cart.itemCount,
previousCount,
});
}
async function clearCart() {
// [Merchant] Clear the real cart in your own system first.
await api.post('/cart/clear');
// [Qredex] Clear IIT/PIT because the merchant cart is now empty.
agent.handleCartEmpty();
}
async function checkout(order) {
// [Qredex] Read the locked PIT that must travel with this order.
const pit = agent.getPurchaseIntentToken();
// [Merchant] Submit your normal order payload and include the PIT so the
// backend can forward order + PIT into Qredex attribution ingestion.
await api.post('/orders', {
...order,
qredex_pit: pit,
});
// [Merchant + Qredex] Reuse the same clear path after checkout succeeds.
await clearCart();
}4. Done!
The agent automatically:
- ✅ Captures
qdx_intentfrom URL - ✅ Stores IIT in browser storage (sessionStorage + cookie fallback)
- ✅ Locks IIT → PIT when the merchant reports a non-empty cart and the state is lockable
- ✅ Clears PIT when the merchant reports that the cart became empty or checkout succeeds
- ✅ Exposes PIT so the merchant/backend can carry it into order attribution
What Merchants Actually Call
| Merchant event | Call | Why |
| -------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| User lands from Qredex link | No manual call required | The agent capturesqdx_intent automatically |
| Cart becomes non-empty | handleCartChange({ itemCount, previousCount }) | Gives Qredex the live cart state so IIT can lock to PIT |
| Cart changes while still non-empty | handleCartChange(...) | Safe retry path on the next merchant-reported non-empty cart event if a previous lock failed |
| Clear cart action | clearCart() -> handleCartEmpty() | Clears IIT/PIT from the live session |
| Need PIT for order submission | getPurchaseIntentToken() | Attach PIT to the order or checkout payload |
| Checkout completes without a cart-empty step | handlePaymentSuccess() | Optional explicit cleanup path |
What is Qredex Agent?
Qredex Agent is a lightweight browser runtime (~5KB minified) that:
- Captures the
qdx_intenttoken from URLs when users arrive via Qredex tracking links - Stores the token securely in browser storage (sessionStorage + cookie fallback)
- Locks the token via Qredex API when the merchant reports a non-empty cart
- Owns attribution state throughout the shopping session
- Exposes the Purchase Intent Token (PIT) so the merchant/backend can carry it into order ingestion
Qredex is not a cart SDK or checkout SDK. The merchant owns commerce actions. Qredex owns deterministic attribution state: capture IIT, lock IIT to PIT when the merchant reports a real cart, then make PIT available for the order path.
Key Features
- Zero dependencies - Pure vanilla JavaScript, works everywhere
- Idempotent operations - Safe to call multiple times
- Retry on failure - Automatically retries lock if it fails
- Storage fallback - Uses sessionStorage first, cookies as fallback
API Reference
Read Tokens
// Get Influence Intent Token (IIT) - captured from URL
QredexAgent.getInfluenceIntentToken() // string | null
// Get Purchase Intent Token (PIT) - locked via API
QredexAgent.getPurchaseIntentToken() // string | null
// Check if IIT exists
QredexAgent.hasInfluenceIntentToken() // boolean
// Check if PIT exists
QredexAgent.hasPurchaseIntentToken() // booleanState Inspection
// Canonical snapshot for browser integrations, support, and workbenches
const state = QredexAgent.getState();
// Returns:
// {
// initialized: boolean,
// lifecycleState: 'idle' | 'running' | 'locking' | 'destroyed',
// lockInProgress: boolean,
// lockAttempts: number,
// hasIIT: boolean,
// hasPIT: boolean,
// iit: string | null,
// pit: string | null,
// cartState: 'unknown' | 'empty' | 'non-empty',
// locked: boolean,
// timestamp: number
// }Tiny Support Panel
<button onclick="renderQredexDebug()">Refresh Qredex state</button>
<pre id="qredex-debug"></pre>
<script>
function renderQredexDebug() {
const snapshot = QredexAgent.getState();
document.getElementById('qredex-debug').textContent =
JSON.stringify(snapshot, null, 2);
}
renderQredexDebug();
</script>Event Handlers (Merchant → Agent)
Tell the agent when events happen:
// Canonical path: report every cart count transition
QredexAgent.handleCartChange({
itemCount: 1,
previousCount: 0,
});
// Add one more item later
QredexAgent.handleCartChange({
itemCount: 2,
previousCount: 1,
});
// Remove one item but keep cart non-empty
QredexAgent.handleCartChange({
itemCount: 1,
previousCount: 2,
});
// Clear cart completely
QredexAgent.handleCartEmpty();
// Optional explicit cleanup if your platform does not emit a cart-empty step
QredexAgent.handlePaymentSuccess();Behavior:
| Event | Example | Agent behavior | | -------------------------------------------- | -------------------- | ------------------------------------------------------------------------- | | Empty cart becomes non-empty | empty cart -> 1 item | Attempts IIT -> PIT lock | | Merchant reports a live non-empty cart again | 1 item -> 2 items | Attempts or retries IIT -> PIT lock if IIT exists and PIT is still absent | | Partial remove | 2 items -> 1 item | No clear; attribution stays attached to the live cart |
Minimum Correct Merchant Sequence
// 1. Load the script (CDN auto-inits by default)
// 2. Merchant reports a real cart transition
QredexAgent.handleCartChange({ itemCount: 1, previousCount: 0 });
// 3. Merchant reads PIT during checkout/order assembly
const pit = QredexAgent.getPurchaseIntentToken();
// 4. Merchant sends order + PIT to their backend
await fetch('/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...order, qredex_pit: pit }),
});
// 5. Merchant clears attribution after checkout if needed
QredexAgent.handlePaymentSuccess();Merchant Integration Checklist
- Load the agent and let it auto-init
- Report every real merchant cart transition with
handleCartChange(...) - Read PIT with
getPurchaseIntentToken()during checkout or order assembly - Send
order + PITto your backend or direct ingestion path - Clear attribution with
handleCartEmpty()orhandlePaymentSuccess()
Tiny Cart Restore Helper
function syncRestoredCart(restoredItemCount) {
if (restoredItemCount > 0) {
// On refresh, treat merchant cart restore as empty -> non-empty unless you
// already track a previous merchant cart count elsewhere.
QredexAgent.handleCartChange({
itemCount: restoredItemCount,
previousCount: 0,
});
}
}Event Listeners (Agent → Merchant)
Optional observability hooks.
Most merchants only need:
onStateChangedto reflect attribution state in UI or debug panelsonErrorto surface integration mistakes and lock failures
Use these only if you want extra lifecycle visibility:
onLockedonClearedonIntentCaptured
Listen for agent events:
// Listen for successful lock
QredexAgent.onLocked(({ purchaseToken, alreadyLocked, timestamp }) => {
console.log('🔒 Locked:', purchaseToken);
console.log('Already locked:', alreadyLocked);
});
// Listen for cleared state
QredexAgent.onCleared(({ reason, timestamp }) => {
console.log('🗑️ Cleared because', reason);
});
// Listen for errors
QredexAgent.onError(({ code, message, context }) => {
console.error('❌ Error in', context, code, message);
});
// Listen for attribution state changes (NEW)
QredexAgent.onStateChanged(({ hasIIT, hasPIT, locked, cartState }) => {
console.log('State changed:', { hasIIT, hasPIT, locked, cartState });
});
// Listen for IIT capture (NEW)
QredexAgent.onIntentCaptured(({ timestamp }) => {
console.log('✅ Intent captured at:', new Date(timestamp));
});Unregister Listeners
const lockedHandler = ({ purchaseToken }) => {
console.log('Locked:', purchaseToken);
};
QredexAgent.onLocked(lockedHandler);
// ... later
QredexAgent.offLocked(lockedHandler);
const errorHandler = ({ code, message }) => {
console.error(code, message);
};
QredexAgent.onError(errorHandler);
// ... later
QredexAgent.offError(errorHandler);Manual Commands
// Manual lock (idempotent - safe to call multiple times)
const result = await QredexAgent.lockIntent();
// Result type:
// { success: true, purchaseToken: 'pit_xxx', alreadyLocked: false }
// { success: false, purchaseToken: null, alreadyLocked: false, error: '...' }
// Clear intent state (after checkout or cart empty)
QredexAgent.clearIntent();Pass meta only if you intentionally want to attach extra merchant context to the lock request.
Lifecycle Methods
// Initialize with custom config
// CDN/IIFE auto-starts on script load; ESM/framework usage should call init() in the browser.
QredexAgent.init({
debug: true,
useMockEndpoint: true,
});
// Check if initialized
QredexAgent.isInitialized(); // boolean
// Destroy agent and clean up listeners (for SPA route changes)
QredexAgent.destroy();
// Alias for destroy()
QredexAgent.stop();Installation
CDN (Recommended)
<script src="https://cdn.qredex.com/agent/v1/qredex-agent.iife.min.js"></script>Versioned URLs:
<!-- Auto-updates to latest v1.x.x -->
<script src="https://cdn.qredex.com/agent/v1/qredex-agent.iife.min.js"></script>
<!-- Pinned version (immutable) -->
<script src="https://cdn.qredex.com/agent/v1.0.0/qredex-agent.iife.min.js"></script>NPM
npm install @qredex/agent// ESM import
import {
handleCartChange,
getPurchaseIntentToken,
onLocked,
} from '@qredex/agent';
// Or default import
import QredexAgent from '@qredex/agent';
QredexAgent.init();For React, Vue, Svelte, or Angular, use the matching wrapper from the Quick Start section above and follow that package README for framework-specific usage.
Configuration
Optional configuration via window.QredexAgentConfig. Set before the script loads:
<script>
window.QredexAgentConfig = {
debug: true,
autoInit: true,
useMockEndpoint: true,
};
</script>
<script src="http://localhost:3000/dist/qredex-agent.iife.dev.min.js"></script>Configuration Options
| Option | Type | Default | Description |
| ----------------- | ------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| autoInit | boolean | true | CDN/IIFE preload only. Default and recommended. Setfalse only for advanced script-tag integrations that need to call init() themselves |
| debug | boolean | false | Non-production logging. Forced tofalse in production |
| useMockEndpoint | boolean | false | ⚠️DEVELOPMENT ONLY for merchant usage. Generates fake PIT tokens locally |
Production does not support runtime endpoint overrides, storage-key overrides, or mock mode. The production bundle always uses the built-in Qredex production lock endpoint and stable storage keys.
The canonical browser path is:
- script loads
- agent auto-inits
- agent auto-captures IIT from
qdx_intentif present
Merchants should not manually capture IIT. That is always agent-owned.
If you disable CDN auto-init, call QredexAgent.init() yourself after the script loads. This is an advanced escape hatch, not the recommended path:
<script>
window.QredexAgentConfig = { autoInit: false, debug: true };
</script>
<script src="https://cdn.qredex.com/agent/v1/qredex-agent.iife.min.js"></script>
<script>
QredexAgent.init();
</script>For real non-production network testing, build the bundle with QREDEX_AGENT_LOCK_ENDPOINT so the endpoint is baked into the staging/dev artifact instead of passed at runtime.
Examples
Quick Test
The fastest way to test Qredex Agent:
# Build the development IIFE bundle, start the local server, and open the test page
npm run exampleIf the browser does not open automatically:
open http://localhost:3000/examples/index.htmlThen:
- Start with the featured CDN card
- Simulate intent URL by adding
?qdx_intent=test123to the CDN page URL and pressing Enter - Add to cart and watch PIT get locked
- Empty cart and watch PIT get cleared
See: examples/README.md for complete example and testing scenarios.
Vanilla JS
const agent = window.QredexAgent;
async function reportCart(previousCount, itemCount) {
// [Qredex] This is the one call that keeps attribution aligned with your cart.
agent.handleCartChange({
previousCount,
itemCount,
});
}
document.querySelector('.add-to-cart').addEventListener('click', async (event) => {
const button = event.currentTarget;
const product = {
id: button.dataset.productId,
price: parseFloat(button.dataset.price),
};
// [Merchant] Capture the cart count before your mutation runs.
const previousCount = cart.itemCount;
// [Merchant] Add the item using your existing cart endpoint.
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify(product),
});
// [Qredex] Tell Qredex what the cart count changed from and to.
reportCart(previousCount, cart.itemCount);
});
document.querySelector('.remove-from-cart').addEventListener('click', async (event) => {
const button = event.currentTarget;
const line = {
id: button.dataset.lineId,
productId: button.dataset.productId,
price: parseFloat(button.dataset.price),
};
// [Merchant] Capture the cart count before your mutation runs.
const previousCount = cart.itemCount;
// [Merchant] Remove the item using your existing cart endpoint.
await fetch(`/api/cart/${line.id}`, {
method: 'DELETE',
});
// [Qredex] Report the resulting cart state back to the agent.
reportCart(previousCount, cart.itemCount);
});
document.querySelector('.clear-cart').addEventListener('click', async () => {
const previousCount = cart.itemCount;
// [Merchant] Clear the real cart in your own system first.
await fetch('/api/cart/clear', {
method: 'POST',
});
// [Qredex] Report the empty-cart transition so attribution is cleared.
reportCart(previousCount, 0);
});Error Handling
Common Issues
Token Not Found
Symptoms: getInfluenceIntentToken() returns null
Solutions:
- Check URL parameter is exactly
?qdx_intent=xxx - Reload the page
- Verify sessionStorage is available (not private browsing)
- Check cookie fallback is working
Lock Fails Silently
Symptoms: Add to cart clicked but PIT not created
Solutions:
- Check IIT exists:
QredexAgent.hasInfluenceIntentToken() - Check Network tab for API errors
- Verify CORS is configured on backend
- Check error listener:
QredexAgent.onError(handler)
Event Listeners Not Firing
Symptoms: Handlers registered but not called
Solutions:
- Verify handler is registered before event occurs
- Check handler function signature matches expected params
- Ensure handler not unregistered accidentally
Debug Mode
Enable debug logging in development, staging, or test:
window.QredexAgentConfig = { debug: true };Production always forces debug back to false and suppresses agent debug/info/warn console output.
Example output:
[QredexAgent] Intent token captured from URL
[QredexAgent] Cart change event received { itemCount: 1, previousCount: 0 }
[QredexAgent] Cart has items, IIT exists, no PIT - attempting lock
[QredexAgent] Sending lock request to: https://api.qredex.com/...
[QredexAgent] Intent locked successfully
[QredexAgent] Purchase token storedCheck Storage
Open DevTools → Application → Storage:
Session Storage:
__qdx_iit- IIT token__qdx_pit- PIT token
Cookies:
__qdx_iit- IIT fallback__qdx_pit- PIT fallback
Browser Support
| Browser | Version | | ------- | ------- | | Chrome | Latest | | Firefox | Latest | | Safari | Latest | | Edge | Latest |
Requirements: ES2020+, sessionStorage, cookies enabled
Documentation
| Document | Description | | -------------------------------------------------------- | ------------------------------------------------------- | | Development | Engineer-only dev CDN, local Core, and sandbox guidance | | Integration Model | Complete integration guide with 2 paths | | API Reference | Full API documentation | | Cart Change Behavior | handleCartChange() state transitions | | Cart Empty Policy | Attribution clearing rationale | | Examples Guide | Examples, local run flow, and testing scenarios | | AGENTS.md | Development guidelines |
Examples Directory
| Example | Description | Quick Start |
| ---------------------------------------------------------------------------- | --------------------------------------- | ----------------- |
| examples/index.html | Example hub for CDN and wrapper pages | npm run example |
| examples/cdn/index.html | Canonical script-tag customer path | Open from the hub |
| examples/wrappers/react/index.html | Real React app using@qredex/react | Open from the hub |
| examples/wrappers/vue/index.html | Real Vue app using@qredex/vue | Open from the hub |
| examples/wrappers/svelte/index.html | Real Svelte app using@qredex/svelte | Open from the hub |
| examples/wrappers/angular/index.html | Real Angular app using@qredex/angular | Open from the hub |
Each example includes:
- A focused integration path
- The same IIT/PIT cart scenario for behavior checks
- Real framework apps for React, Vue, Svelte, and Angular
- Debugging guide
See: examples/README.md for detailed example and testing instructions.
License
Apache-2.0
Support
For questions: [email protected]
