Overview
Data providers allow you to integrate your offer redirect URLs and cashback offers directly into AI chat conversations. When the AI recommends retailers, cashback information is automatically displayed to users. When users click on merchant or product links, your offer redirect URLs are pulled, creating seamless monetization opportunities.
Data providers are recommended for full functionality of the chat interface, especially when displaying merchant recommendations.
Provider types
The SDK supports two types of data providers:
Offer Redirect URL Provider : Returns offer redirect URLs for individual merchant URLs
Merchant Cashback Provider : Returns cashback information for multiple merchants in batch
Offer redirect URL provider
Type definition
type OfferRedirectUrlProvider = (
url : string
) => Promise < string | null | undefined > | string | null | undefined ;
Purpose
Converts regular merchant URLs into your offer redirect URLs. Called when the user clicks a merchant link or product link that the AI mentions or recommends for a specific retailer or product.
Implementation
import type { OfferRedirectUrlProvider } from '@sleek/ai-chat-sdk' ;
const offerRedirectUrlProvider : OfferRedirectUrlProvider = async ( url ) => {
try {
// Call your offer redirect API
const response = await fetch (
`https://api.yourapp.com/offer-redirect?url= ${ encodeURIComponent ( url ) } ` ,
{
headers: {
'Authorization' : `Bearer ${ YOUR_API_TOKEN } ` ,
},
}
);
if ( ! response . ok ) {
console . error ( 'Offer redirect API error:' , response . status );
return null ;
}
const data = await response . json ();
// Return the offer redirect URL or null if unavailable
return data . offerRedirectUrl || null ;
} catch ( error ) {
console . error ( 'Error fetching offer redirect URL:' , error );
return null ; // Always return null on error
}
};
url : The merchant URL to convert (e.g., "https://www.amazon.com")
Output
string : The offer redirect URL
null/undefined : No offer redirect URL available for this merchant
Best practices
1. Handle errors gracefully
Always return null on errors. Never throw exceptions or let errors propagate: const offerRedirectUrlProvider : OfferRedirectUrlProvider = async ( url ) => {
try {
const response = await fetch ( /* ... */ );
return response . offerRedirectUrl || null ;
} catch ( error ) {
console . error ( 'Error:' , error );
return null ; // Critical: return null, don't throw
}
};
Cache offer redirect URLs to improve performance and reduce API calls: const offerRedirectUrlCache = new Map < string , string >();
const offerRedirectUrlProvider : OfferRedirectUrlProvider = async ( url ) => {
// Check cache first
if ( offerRedirectUrlCache . has ( url )) {
return offerRedirectUrlCache . get ( url ) ! ;
}
try {
const response = await fetch ( /* ... */ );
const offerRedirectUrl = response . offerRedirectUrl ;
if ( offerRedirectUrl ) {
// Cache the result
offerRedirectUrlCache . set ( url , offerRedirectUrl );
}
return offerRedirectUrl || null ;
} catch ( error ) {
return null ;
}
};
3. Support multiple domains
Handle different merchant domains and URL formats: const offerRedirectUrlProvider : OfferRedirectUrlProvider = async ( url ) => {
try {
const parsedUrl = new URL ( url );
const domain = parsedUrl . hostname . replace ( 'www.' , '' );
// Handle redirect URLs for different affiliate networks
if ( domain === 'amazon.com' ) {
return buildAmazonOfferRedirect ( url );
} else if ( domain === 'walmart.com' ) {
return buildWalmartOfferRedirect ( url );
} else {
// Generic offer redirect URL API
return fetchGenericOfferRedirect ( url );
}
} catch ( error ) {
return null ;
}
};
4. Keep response times fast
Aim for <500ms response times: const offerRedirectUrlProvider : OfferRedirectUrlProvider = async ( url ) => {
const controller = new AbortController ();
const timeoutId = setTimeout (() => controller . abort (), 500 );
try {
const response = await fetch ( apiUrl , {
signal: controller . signal ,
});
clearTimeout ( timeoutId );
return response . offerRedirectUrl || null ;
} catch ( error ) {
clearTimeout ( timeoutId );
if ( error . name === 'AbortError' ) {
console . warn ( 'Offer redirect URL fetch timeout' );
}
return null ;
}
};
Merchant cashback provider
Type definition
type MerchantCashbackProvider = (
merchantUrls : string []
) => Promise < Record < string , MerchantCashback >> | Record < string , MerchantCashback >;
interface MerchantCashback {
text : string ; // e.g., "5% cash back", "Up to $10 back", "$25 bonus"
}
Purpose
Provides cashback information for multiple merchants in a single batch request. Called when the AI displays a list of retailers or product recommendations.
Implementation
import type {
MerchantCashbackProvider ,
MerchantCashback ,
} from '@sleek/ai-chat-sdk' ;
const merchantCashbackProvider : MerchantCashbackProvider = async ( urls ) => {
try {
// Batch API call for multiple merchants
const response = await fetch ( 'https://api.yourapp.com/cashback/batch' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ YOUR_API_TOKEN } ` ,
},
body: JSON . stringify ({ merchantUrls: urls }),
});
if ( ! response . ok ) {
console . error ( 'Cashback API error:' , response . status );
return {};
}
const data = await response . json ();
// Transform API response to required format
const cashbackData : Record < string , MerchantCashback > = {};
for ( const [ url , info ] of Object . entries ( data . cashback || {})) {
cashbackData [ url ] = {
text: info . cashbackText || info . text ,
};
}
return cashbackData ;
} catch ( error ) {
console . error ( 'Error fetching cashback:' , error );
return {}; // Always return {} on error
}
};
merchantUrls : Array of merchant URLs (e.g., ["https://amazon.com", "https://walmart.com"])
Output
A record mapping merchant URLs to cashback information:
{
"https://amazon.com" : { text: "5% cash back" },
"https://walmart.com" : { text: "Up to $10 back" },
"https://target.com" : { text: "$25 welcome bonus" }
}
If a merchant doesn’t have cashback, simply omit it from the result.
Best practices
1. Handle errors gracefully
Always return {} on errors: const merchantCashbackProvider : MerchantCashbackProvider = async ( urls ) => {
try {
const response = await fetch ( /* ... */ );
return response . cashback || {};
} catch ( error ) {
console . error ( 'Error:' , error );
return {}; // Critical: return {}, don't throw
}
};
Cache cashback data with TTL (time-to-live): interface CacheEntry {
data : MerchantCashback ;
expiresAt : number ;
}
const cashbackCache = new Map < string , CacheEntry >();
const CACHE_TTL = 60 * 60 * 1000 ; // 1 hour
const merchantCashbackProvider : MerchantCashbackProvider = async ( urls ) => {
const now = Date . now ();
const result : Record < string , MerchantCashback > = {};
const urlsToFetch : string [] = [];
// Check cache for each URL
for ( const url of urls ) {
const cached = cashbackCache . get ( url );
if ( cached && cached . expiresAt > now ) {
result [ url ] = cached . data ;
} else {
urlsToFetch . push ( url );
}
}
// Fetch uncached URLs
if ( urlsToFetch . length > 0 ) {
try {
const response = await fetch ( /* ... */ , {
body: JSON . stringify ({ merchantUrls: urlsToFetch }),
});
for ( const [ url , cashback ] of Object . entries ( response . cashback )) {
result [ url ] = cashback ;
cashbackCache . set ( url , {
data: cashback ,
expiresAt: now + CACHE_TTL ,
});
}
} catch ( error ) {
console . error ( 'Error:' , error );
}
}
return result ;
};
3. Normalize cashback text
Provide consistent, user-friendly text: function formatCashbackText ( cashback : any ) : string {
if ( typeof cashback === 'string' ) {
return cashback ;
}
if ( cashback . percentage ) {
return ` ${ cashback . percentage } % cash back` ;
}
if ( cashback . amount ) {
return `$ ${ cashback . amount } cash back` ;
}
if ( cashback . upTo ) {
return `Up to $ ${ cashback . upTo } back` ;
}
return 'Cash back available' ;
}
const merchantCashbackProvider : MerchantCashbackProvider = async ( urls ) => {
const response = await fetch ( /* ... */ );
const result : Record < string , MerchantCashback > = {};
for ( const [ url , cashback ] of Object . entries ( response . data )) {
result [ url ] = {
text: formatCashbackText ( cashback ),
};
}
return result ;
};
4. Handle batch size limits
Some APIs have batch size limits. Split large requests: const MAX_BATCH_SIZE = 50 ;
const merchantCashbackProvider : MerchantCashbackProvider = async ( urls ) => {
if ( urls . length === 0 ) return {};
const result : Record < string , MerchantCashback > = {};
// Split into batches
for ( let i = 0 ; i < urls . length ; i += MAX_BATCH_SIZE ) {
const batch = urls . slice ( i , i + MAX_BATCH_SIZE );
try {
const response = await fetch ( /* ... */ , {
body: JSON . stringify ({ merchantUrls: batch }),
});
Object . assign ( result , response . cashback || {});
} catch ( error ) {
console . error ( `Error fetching batch ${ i / MAX_BATCH_SIZE + 1 } :` , error );
}
}
return result ;
};
Complete example
Here’s a production-ready implementation with caching, error handling, and performance optimization:
import type {
OfferRedirectUrlProvider ,
MerchantCashbackProvider ,
MerchantCashback ,
} from '@sleek/ai-chat-sdk' ;
// ----- Offer Redirect URL Provider -----
const offerRedirectUrlCache = new Map < string , string >();
const offerRedirectUrlProvider : OfferRedirectUrlProvider = async ( url ) => {
// Check cache
if ( offerRedirectUrlCache . has ( url )) {
return offerRedirectUrlCache . get ( url ) ! ;
}
try {
const controller = new AbortController ();
const timeoutId = setTimeout (() => controller . abort (), 500 );
const response = await fetch (
`https://api.yourapp.com/offer-redirect?url= ${ encodeURIComponent ( url ) } ` ,
{
headers: {
'Authorization' : `Bearer ${ process . env . API_TOKEN } ` ,
},
signal: controller . signal ,
}
);
clearTimeout ( timeoutId );
if ( ! response . ok ) {
return null ;
}
const data = await response . json ();
const offerRedirectUrl = data . offerRedirectUrl ;
if ( offerRedirectUrl ) {
offerRedirectUrlCache . set ( url , offerRedirectUrl );
}
return offerRedirectUrl || null ;
} catch ( error ) {
if ( error . name !== 'AbortError' ) {
console . error ( 'Offer redirect URL error:' , error );
}
return null ;
}
};
// ----- Merchant Cashback Provider -----
interface CashbackCacheEntry {
data : MerchantCashback ;
expiresAt : number ;
}
const cashbackCache = new Map < string , CashbackCacheEntry >();
const CASHBACK_CACHE_TTL = 60 * 60 * 1000 ; // 1 hour
const merchantCashbackProvider : MerchantCashbackProvider = async ( urls ) => {
if ( urls . length === 0 ) return {};
const now = Date . now ();
const result : Record < string , MerchantCashback > = {};
const urlsToFetch : string [] = [];
// Check cache
for ( const url of urls ) {
const cached = cashbackCache . get ( url );
if ( cached && cached . expiresAt > now ) {
result [ url ] = cached . data ;
} else {
urlsToFetch . push ( url );
cashbackCache . delete ( url ); // Clean up expired entry
}
}
// Fetch uncached URLs
if ( urlsToFetch . length > 0 ) {
try {
const controller = new AbortController ();
const timeoutId = setTimeout (() => controller . abort (), 1000 );
const response = await fetch ( 'https://api.yourapp.com/cashback' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
'Authorization' : `Bearer ${ process . env . API_TOKEN } ` ,
},
body: JSON . stringify ({ merchantUrls: urlsToFetch }),
signal: controller . signal ,
});
clearTimeout ( timeoutId );
if ( response . ok ) {
const data = await response . json ();
for ( const [ url , cashback ] of Object . entries < any >( data . cashback || {})) {
const cashbackData : MerchantCashback = {
text: cashback . text || cashback . cashbackText || 'Cash back available' ,
};
result [ url ] = cashbackData ;
cashbackCache . set ( url , {
data: cashbackData ,
expiresAt: now + CASHBACK_CACHE_TTL ,
});
}
}
} catch ( error ) {
if ( error . name !== 'AbortError' ) {
console . error ( 'Cashback fetch error:' , error );
}
}
}
return result ;
};
// ----- SDK Initialization -----
import { initializeAiChatSdk } from '@sleek/ai-chat-sdk' ;
initializeAiChatSdk ( 'your-zero-click-api-key' , {
// ... other config
dataProviders: {
offerRedirectUrlProvider ,
merchantCashbackProvider ,
},
});
Testing data providers
Unit testing
Test your providers with mock data:
import { describe , it , expect , vi } from 'vitest' ;
describe ( 'offerRedirectUrlProvider' , () => {
it ( 'returns offer redirect URL for supported merchant' , async () => {
const url = 'https://amazon.com' ;
const result = await offerRedirectUrlProvider ( url );
expect ( result ). toMatch ( /amazon \. com/ );
expect ( result ). toContain ( 'tag=' );
});
it ( 'returns null for unsupported merchant' , async () => {
const url = 'https://unknown-merchant.com' ;
const result = await offerRedirectUrlProvider ( url );
expect ( result ). toBeNull ();
});
it ( 'handles API errors gracefully' , async () => {
// Mock API failure
global . fetch = vi . fn (). mockRejectedValue ( new Error ( 'Network error' ));
const result = await offerRedirectUrlProvider ( 'https://amazon.com' );
expect ( result ). toBeNull ();
});
});
describe ( 'merchantCashbackProvider' , () => {
it ( 'returns cashback for multiple merchants' , async () => {
const urls = [ 'https://amazon.com' , 'https://walmart.com' ];
const result = await merchantCashbackProvider ( urls );
expect ( result ). toHaveProperty ( 'https://amazon.com' );
expect ( result [ 'https://amazon.com' ]. text ). toBeTruthy ();
});
it ( 'handles empty array' , async () => {
const result = await merchantCashbackProvider ([]);
expect ( result ). toEqual ({});
});
it ( 'handles API errors gracefully' , async () => {
global . fetch = vi . fn (). mockRejectedValue ( new Error ( 'Network error' ));
const result = await merchantCashbackProvider ([ 'https://amazon.com' ]);
expect ( result ). toEqual ({});
});
});
Manual testing
Test in the browser console:
// Test offer redirect URL provider
const offerRedirectUrl = await offerRedirectUrlProvider ( 'https://amazon.com' );
console . log ( 'Offer redirect URL:' , offerRedirectUrl );
// Test cashback provider
const cashback = await merchantCashbackProvider ([
'https://amazon.com' ,
'https://walmart.com' ,
'https://target.com' ,
]);
console . log ( 'Cashback data:' , cashback );
Common issues
Provider not being called
Symptom : Data providers never executeCauses :
Chat panel not opened
AI not recommending merchants
Provider not configured properly
Solution : Open the chat and ask about products or merchants to trigger provider calls
Symptom : SDK stops working after provider errorCauses :
Throwing exceptions instead of returning null/
Unhandled promise rejections
Solution : Wrap all code in try-catch, always return null or on errors
Next steps