Search for answers or browse our knowledge base.
Instant Reviews – Technical implementation

Overview
The Instant Review Widget allows verified customers to leave a review directly on a store page, without an email invitation. It authenticates the customer using an encrypted Customer Order Token, which ties the review to a specific order and customer. This prevents review manipulation and ensures that only real buyers can submit reviews.
The widget is ideal for scenarios where you want to collect reviews at the point of order confirmation (e.g., order confirmation page, account order history).
Integration Guide
Prerequisite: Ensure Orders Are Processed via Lipscore
Only invitations (orders) that exist in Lipscore can be reviewed:
- The order must be sent/imported to Lipscore
- The internal_customer_id and internal_order_id (in Customer Order Token) must match what Lipscore has stored
If the invitation is not found, the widget will not render.
Generate a Customer Order Token (Server-Side)
You must generate an encrypted token on your server using:
- internal_customer_id
- internal_order_id
- Current timestamp
️ Requirements:
- Must be generated server-side
- Must use your Secret API Key
- Expires after 72 hours
See Customer Order Token section for full implementation examples.
Add the Widget to Your Page
Render one widget per product using the encrypted token and product identifiers.
Example
<div class="lipscore-instant-review"
data-ls-customer-order-token="ENCRYPTED_TOKEN_HERE"
data-ls-product-id="3153307696821"
data-ls-sku="272905072-8"
data-ls-product-url="https://example.com/products/product-1"
data-ls-variant-id="18176-5-sv"
data-ls-gtin="7332741120000"
data-ls-mpn="FFC13T580600"
></div>
Widget Configuration
Widget HTML Structure
The widget is initialized using a <div> with:
- Class: lipscore-instant-review
- Required attributes:
- data-ls-customer-order-token
- data-ls-product-id
- At least one product variant identifier (e.g., data-ls-sku, data-ls-gtin) is required. For best results, use the same identifiers you provided when sending the invitation data to Lipscore.
Full Example
<div class="lipscore-instant-review"
data-ls-customer-order-token="ENCRYPTED_TOKEN_HERE"
data-ls-product-id="3153307696821"
data-ls-sku="272905072-8"
data-ls-product-url="https://example.com/products/product-1"
data-ls-variant-id="18176-5-sv"
data-ls-gtin="7332741120000"
data-ls-mpn="FFC13T580600"
></div>
Widget Attributes
| Attribute | Required | Description |
| data-ls-customer-order-token | Yes | Encrypted token identifying the customer and order. |
| data-ls-product-id | Yes | Your internal product identifier. |
| data-ls-product-name | Yes | Product name shown in the review modal. Should match the visible product name near the widget (e.g., order page). |
| data-ls-sku | Yes* | Product SKU. Improves product matching accuracy. |
| data-ls-variant-id | Yes* | Variant identifier (for products with multiple variants). Improves product matching accuracy. |
| data-ls-gtin | Yes* | Global Trade Item Number (EAN/UPC/ISBN). Improves product matching accuracy. |
| data-ls-mpn | Yes* | Manufacturer Part Number. Improves product matching accuracy. |
| data-ls-product-url | Yes* | Product page URL. Improves product matching accuracy. |
Reviewing Multiple Products
Multiple Products in One Order
If an order contains multiple products, render one widget per product or product variant.
All widgets share the same data-ls-customer-order-token, but must include identifiers that uniquely match each product (or variant) from the order.
If your products have variants (e.g., size, color), each variant should be treated as a separate item and identified with its own product variant identifiers.
Example
<!-- Product 1 -->
<div class="lipscore-instant-review"
data-ls-customer-order-token="ENCRYPTED_TOKEN"
data-ls-product-id="product-001"
data-ls-sku="TSHIRT-BLACK-M"
data-ls-product-name="Classic T-Shirt – Black / M"
></div>
<!-- Product 1 (different variant) -->
<div class="lipscore-instant-review"
data-ls-customer-order-token="ENCRYPTED_TOKEN"
data-ls-product-id="product-001"
data-ls-sku="TSHIRT-BLACK-L"
data-ls-product-name="Classic T-Shirt – Black / L"
></div>
<!-- Product 2 -->
<div class="lipscore-instant-review"
data-ls-customer-order-token="ENCRYPTED_TOKEN"
data-ls-product-id="product-002"
data-ls-sku="HOODIE-GREY-M"
data-ls-product-name="Premium Hoodie – Grey / M"
></div>
Important:
- Use the same token for all items in the order
- Ensure each widget has unique product identifiers
- Provide variant-specific identifiers (e.g., SKU) when applicable for accurate matching
Customer Order Token
Purpose and Security
The Customer Order Token is an encrypted, time-limited credential that authenticates a customer for a specific order.
It replaces email-based review invitations and ensures that only verified customers can submit reviews.
Key properties:
- Only your server can generate valid tokens (uses Secret API Key)
- Tokens are tied to a specific order and customer
- Tokens expire after 72 hours
- Tokens are tamper-proof (AES-256-GCM + HMAC)
Token Payload
The token contains the following JSON payload:
{
"internal_customer_id": "cust_12345",
"internal_order_id": "ord_67890",
"timestamp": 1741510800
}
| Field | Type | Description |
| internal_customer_id | String | Your internal customer identifier |
| internal_order_id | String | Your internal order identifier |
| timestamp | Integer | Unix timestamp (seconds since epoch) |
Encryption and Signing
The token is encrypted using:
- AES-256-GCM (encryption + authentication)
- HMAC-SHA256 for signing
The encryption key is derived from your Secret API Key.
Secret API Key
Where to find it
- Log in to Lipscore Dashboard
- Go to Settings → General -> API settings
- Copy your Secret API Key
Important: Must be used server-side only. Never expose in:
- HTML
- JavaScript
- Client-side code
Token Generation (Pseudocode)
SECRET_API_KEY = "your-secret-api-key"
SALT = "context-encryption-v1" # Fixed string
KEY_LENGTH = 32 # 256 bits for AES-256
# 1. Derive encryption key from Secret API Key
key = PBKDF2(SECRET_API_KEY, salt=SALT, iterations=65536, key_length=KEY_LENGTH, hash=SHA1)
# 2. Build the JSON payload
payload = JSON.encode({
"internal_customer_id": "cust_12345",
"internal_order_id": "ord_67890",
"timestamp": current_unix_timestamp()
})
# 3. Generate a random 12-byte IV (nonce)
iv = random_bytes(12)
# 4. Encrypt with AES-256-GCM
ciphertext, auth_tag = AES_256_GCM_encrypt(key, iv, payload)
# 5. Encode as URL-safe Base64
# The final token format is:
# Base64url(ciphertext) + "--" + Base64url(iv) + "--" + Base64url(auth_tag)
# Then the whole thing is signed with HMAC-SHA256 using the same key:
# encrypted_message + "--" + Base64url(HMAC_SHA256(key, encrypted_message))
token = base64url(ciphertext) + "--" + base64url(iv) + "--" + base64url(auth_tag)
signature = HMAC_SHA256(key, token)
final_token = token + "--" + base64url(signature)
Token Format
The final token uses the following format:
base64url(ciphertext)–base64url(iv)–base64url(auth_tag)–base64url(signature)
Token Validation (Lipscore Server-Side)
When Lipscore receives a token, it performs the following checks:
- Decrypts the token using your Secret API Key
- Verifies the HMAC signature (detects tampering)
- Validates timestamp:
- Not older than 72 hours
- Not more than 1 minute in the future
- Ensures internal_customer_id is present
- Ensures internal_order_id is present
- Matches the invitation using both identifiers
If any check fails, the request is rejected.
Troubleshooting
| Problem | Cause | Solution |
| Widget does not appear | Missing or invalid token | Ensure token is generated server-side and added to HTML |
| 401 Unauthorized | Invalid key or expired token | Verify Secret API Key and timestamp (≤ 72h) |
| 404 Not Found | Purchase not found | Ensure customer and order IDs match Lipscore data |
| 401 “More than one invitation” | Duplicate order/customer combination | Ensure each order + customer pair is unique |

