Create dynamic checkout sessions programmatically with full control over payment flow and tracking.
Checkout sessions give you programmatic control over the payment flow. Unlike static payment links, checkout sessions are generated dynamically, allowing you to:
Pass custom tracking IDs for each payment
Pre-fill customer information like email
Set dynamic success URLs based on your app’s context
// app/page.tsx'use client'; // Optional: CreemCheckout also works in Server Componentsimport { CreemCheckout } from '@creem_io/nextjs';export function CheckoutButton() { return ( <CreemCheckout productId="prod_YOUR_PRODUCT_ID" referenceId="user_123" // Optional: Track this payment in your system > <button>Buy Now</button> </CreemCheckout> );}
The CreemCheckout component automatically handles the checkout session creation and redirects the user to the payment page.
The signature query parameter allows you to verify that the redirect came from Creem. This prevents malicious users from spoofing successful payment redirects.
The signature is generated using HMAC-SHA256 with your API key as the secret. Null or undefined values are excluded from the signature string — only include parameters that have actual values.
import hmacimport hashlibfrom urllib.parse import parse_qsdef verify_redirect_signature(params: dict, api_key: str) -> bool: """ Verify the redirect signature from Creem checkout. Args: params: Dictionary of query parameters from the redirect URL api_key: Your Creem API key Returns: True if signature is valid, False otherwise """ signature = params.get('signature', [''])[0] # Filter out null/None values and the signature itself filtered = { k: v[0] if isinstance(v, list) else v for k, v in params.items() if k != 'signature' and v and v[0] not in (None, 'null', '') } # Sort and build the signature string sorted_params = '&'.join( f'{k}={v}' for k, v in sorted(filtered.items()) ) expected_signature = hmac.new( api_key.encode(), sorted_params.encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected_signature)# Usage in Flask@app.route('/success')def success(): params = request.args.to_dict(flat=False) if not verify_redirect_signature(params, os.environ['CREEM_API_KEY']): return 'Invalid signature', 401 # Proceed with success page...
Copy
package mainimport ( "crypto/hmac" "crypto/sha256" "encoding/hex" "net/url" "sort" "strings")func verifyRedirectSignature(params url.Values, apiKey string) bool { signature := params.Get("signature") // Build list of non-null parameters (excluding signature) var keys []string for k := range params { if k != "signature" { v := params.Get(k) if v != "" && v != "null" { keys = append(keys, k) } } } sort.Strings(keys) // Build signature string var pairs []string for _, k := range keys { pairs = append(pairs, k+"="+params.Get(k)) } sortedParams := strings.Join(pairs, "&") // Generate expected signature mac := hmac.New(sha256.New, []byte(apiKey)) mac.Write([]byte(sortedParams)) expectedSignature := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(signature), []byte(expectedSignature))}
Important: Parameters with null values (like order_id for subscription-only checkouts, or subscription_id for one-time payments) must be excluded from the signature string. Including them as "order_id=null" will cause verification to fail.
// Email is automatically set from the authenticated userconst { data } = await authClient.creem.createCheckout({ productId: 'prod_YOUR_PRODUCT_ID', customer: { email: 'user@example.com', // Optional: if you want to overwrite the session },});
The Better Auth integration automatically uses the authenticated user’s email.