Guides
Providers and connections
Grizzlee hosts account authentication and company storage. Merchants connect payment providers under Settings, then Connections, and manage server keys and outgoing webhooks under Developers. You do not need Supabase keys, AWS credentials or a database connection to integrate your business.
Account access
Create an account at Grizzlee, verify your email, and create a company. Company owners and admins manage credentials and provider connections. A server API key selects its company and grants only its selected scopes.
Use separate keys for separate backends. Copy each secret when you create it, store it on that backend, and delete it in Developers when it is no longer needed. Deleting a key revokes it right away. A publishable tracking key belongs in a website or mobile app and grants referral capture only.
Stripe
Create a Stripe connection with Connect provider. The form shows the connection's Provider webhook URL first. Register it in your Stripe account for charge.succeeded, charge.refunded, refund.created and refund.updated, then paste the signing secret Stripe shows for it and a private Stripe key with charge and refund read access. The URL starts working once you choose Save connection, so keep the form open until then.
To record paid invoices as orders, also subscribe to invoice.paid, credit_note.created and credit_note.voided, and give the key read access to invoices, subscriptions, customers, Checkout Sessions and credit notes. See Stripe subscriptions.
Grizzlee counts each Stripe customer's purchases. Add grizzlee_customer_id metadata to use your own customer IDs instead.
Choose whether percentage rewards use the captured charge amount or a merchant amount in Charge metadata. Captured amount includes any tax and shipping charged. Metadata mode lets your server report a different base, such as a merchant fee. Put metadata on the Charge, rather than assuming PaymentIntent metadata will update an existing Charge.
For Stripe transfers, the private key also needs Transfers write permission. Subscribe to transfer.reversed on the merchant account webhook endpoint so full and partial reversals update partner balances. Select payout.reversed on your outgoing Grizzlee endpoint to receive the returned amount. The merchant funds its Stripe balance and the partner needs a connected account. A transfer moves money into that account. Stripe handles the later bank payout. See Stripe integration and Stripe transfers.
For partner promo codes, grant Coupons write and Promotion Codes write, plus Invoices and Checkout Sessions read. See supported provider discounts.
Shopify
Create a Shopify connection with the exact your-shop.myshopify.com domain, webhook signing secret and Admin API token. Grant order and customer read access, including read_all_orders. Register the displayed webhook URL for orders/paid and orders/updated. Paid orders also pass their browser_ip to the fraud rules as a hashed IP signal.
Partner discount creation, replacement and deactivation require write_discounts. Grizzlee credits applied partner codes through checkout candidate selection. Unsupported friend incentives return an error.
The adapter sends the purchase subtotal after discounts and before tax and shipping as commissionableCents. It sends the full order payment as optional totalCents for reporting. It verifies the shop and reads the customer's full order history. Use the server API when your rules require another amount or an order count based only on successful purchases. See Shopify integration.
The connection uses credentials you configure. Grizzlee does not currently offer a Shopify App Store installation or Shopify OAuth setup.
Paddle
Paddle Billing connections record each completed transaction as an order, subscription renewals included. Paddle Classic is not supported.
Set up Paddle
- In Grizzlee, open Settings, then Connections, choose Connect provider and pick Paddle. Copy the Provider webhook URL. Keep the form open.
- In Paddle, open Notifications under Developer tools and choose New destination. Choose the URL type, paste the URL and select
transaction.completed,adjustment.createdandadjustment.updated. Save the destination. - Copy the secret key Paddle shows for the destination. It starts with
pdl_ntfset_. Paste it into Paddle secret key. - In Paddle, open Authentication under Developer tools and create an API key with only the Adjustments read permission. Paste it into Paddle API key. Grizzlee lists adjustments with it when a refund arrives and checks it when you save.
- Choose Save connection. A key Paddle refuses stops the save with the reason.
Use a Paddle sandbox account and a separate Grizzlee company to test. A sandbox API key starts with pdl_sdbx_ and Grizzlee then calls the sandbox API. Buy something in the sandbox checkout, refund part of it, and check Incoming events on the Events tab of Developers. Paddle's webhook simulator sends sample transactions with made-up IDs. Grizzlee records them as real orders in that company.
What a Paddle order earns
Paddle is the merchant of record, so its totals include tax. Grizzlee sends details.totals.total minus details.totals.tax as commissionableCents. That is the amount after discounts and before tax. Paddle's fee is not taken off. totalCents is the full total. The customer is paddle: followed by the Paddle customer ID, so renewals stay with the customer who was referred. The order time is billed_at.
Each adjustment event makes Grizzlee list every adjustment of the transaction and send the cumulative refund. Approved refunds, credits and chargebacks count. Pending and rejected refunds do not. A reversed chargeback or credit stops counting, and the reward is restored. The commissionable refund is the sum of the adjustments' pre-tax totals.subtotal.
Receipts at a Paddle checkout
Grizzlee reads custom_data.grizzlee on the transaction. It holds the script tag's checkout reference or a full receipt. The other grizzlee_ keys work as they do in Stripe metadata, such as grizzlee_candidates. Paddle copies custom data to the subscription and its renewals. A subscription payment after the first reads it only when Grizzlee has no earlier order from the customer, so a renewal does not present the first receipt again, but the first paid payment after a free trial still does.
With the script tag, HTML checkout buttons with the paddle_button class get "grizzlee" merged into their data-custom-data. If your code opens the checkout, pass the reference yourself:
<script>
Paddle.Checkout.open({
items: [{ priceId: "pri_REPLACE", quantity: 1 }],
customData: { grizzlee: window.Grizzlee.checkoutReference() },
});
</script>Without the script tag, your server or page puts the reference from GrizzleeTrackingClient in the same field.
Paddle limits
- Orders must be in the company currency. Paddle shows local prices by default, and a transaction in another currency waits in needs action. Limit the Paddle checkout to your Grizzlee currency.
- A transaction without a Paddle customer waits in needs action.
- Partner promo codes are not available for Paddle.
- Email matching to an earlier referred customer is not available yet.
Lemon Squeezy
Lemon Squeezy connections record paid orders and subscription renewals. Stripe owns Lemon Squeezy and plans to move its merchants to Stripe Managed Payments. Lemon Squeezy still takes new stores, and its webhooks work as described here.
Set up Lemon Squeezy
- In Grizzlee, open Settings, then Connections, choose Connect provider and pick Lemon Squeezy. Copy the Provider webhook URL. Keep the form open.
- In Lemon Squeezy, open Settings, then Webhooks, and add a webhook. Paste the URL as the callback URL and type a signing secret of 6 to 40 characters. Select
order_created,order_refunded,subscription_payment_successandsubscription_payment_refunded. Save the webhook. - Paste the same signing secret into Webhook signing secret and choose Save connection.
Lemon Squeezy needs no API key. Its API keys can do everything in the store, and the webhook already carries the whole order and every refund. Lemon Squeezy keeps test mode and live mode webhooks apart. Test with a test mode webhook pointing at a separate Grizzlee company. Webhooks on the Lemon Squeezy settings page show what was sent and can be resent.
What a Lemon Squeezy order earns
Lemon Squeezy is the merchant of record, so its totals include tax. Grizzlee sends total minus tax as commissionableCents. That is the amount after discounts and before tax. totalCents is the full total. The customer is the store and Lemon Squeezy customer ID, so renewals stay with the referred customer.
order_created records the order. The first payment of a subscription arrives with it, so subscription_payment_success records only renewals and plan changes. Refund events carry the cumulative refunded_amount. Grizzlee takes the order's pre-tax share of it, rounded down to the cent, and a full refund reverses the order.
Receipts at a Lemon Squeezy checkout
Grizzlee reads meta.custom_data.grizzlee, which a checkout link sets with checkout[custom][grizzlee]=<reference>. The script tag adds it to every link to a lemonsqueezy.com checkout on the page, including overlay buttons. When your server creates checkouts with the Lemon Squeezy API, put the reference in checkout_data.custom.grizzlee. Renewal invoices read it too when Lemon Squeezy sends it. A subscription payment after the first reads it only when Grizzlee has no earlier order from the customer, so a renewal does not present the first receipt again, but the first paid payment after a free trial still does.
Lemon Squeezy limits
- Orders must be in the company currency. An order in another currency waits in needs action.
- Refunding the first payment of a subscription through the subscription invoice alone is not counted. Refund it from the order, which sends
order_refunded. - Partner promo codes and email matching are not available for Lemon Squeezy.
Chargebee
Chargebee connections record paid invoices as orders, subscription renewals included. Chargebee signs no webhooks, so Grizzlee checks the user name and password Chargebee sends with each one.
Set up Chargebee
- In Grizzlee, open Settings, then Connections, choose Connect provider and pick Chargebee. Enter your Chargebee site, the part before
.chargebee.com. The form shows the Provider webhook URL, the User namegrizzleeand a Password it made for this connection. Keep the form open. - In Chargebee, open Settings, then Configure Chargebee, then API Keys and Webhooks. On the Webhooks tab, choose Add Webhook. Paste the URL, turn on Protect webhook URL with basic authentication, and paste the user name and password.
- Under Events to Send, select
payment_succeeded,payment_refunded,credit_note_created,credit_note_updatedandcredit_note_deleted. Turn on Exclude card information. Save the webhook. - On the API Keys tab, create a read-only key and paste it into Chargebee API key. Grizzlee lists an invoice's credit notes with it when a refund arrives and checks it when you save.
- Choose Save connection.
Test with your Chargebee test site, such as acme-test, and a separate Grizzlee company. Test Webhook in Chargebee sends sample events. A sample payment becomes a real order in that company.
What a Chargebee order earns
The order is the invoice. When payment_succeeded shows the invoice paid, Grizzlee records it. An invoice paid in parts becomes an order with the payment that finishes it. commissionableCents is amount_paid less its share of tax, which is the line amounts after discounts and without tax when the whole invoice was paid in money. Credits applied from earlier credit notes are not money collected. totalCents is amount_paid. The customer is the site and Chargebee customer ID.
Each credit note or refund event makes Grizzlee list the invoice's credit notes and send the cumulative refund. Money refunded from a credit note counts. Credit kept on the customer's account does not, because it lowers what a later invoice collects instead. Voided and deleted credit notes do not count.
Receipts at a Chargebee checkout
Grizzlee reads the reference from a cf_grizzlee custom field or a grizzlee key in meta_data, on the subscription or the customer. A subscription payment after the first reads it only when Grizzlee has no earlier order from the customer, so a renewal does not present the first receipt again, but the first paid payment after a free trial still does. To use a custom field, create one named Grizzlee on subscriptions in Chargebee, so its API name is cf_grizzlee.
The script tag does not fill Chargebee checkouts, because the field must exist in Chargebee first and the script can't tell whether it does. Your server sets it when it creates the hosted page, such as subscription[cf_grizzlee] or subscription[meta_data] with the reference from GrizzleeTrackingClient. With Chargebee's drop-in script, set the field before checkout opens, as Chargebee's guide to prefilled custom fields shows:
<script>
window.Grizzlee = window.Grizzlee || [];
Grizzlee.push(function (grizzlee) {
const plan = document.querySelector("[data-cb-item-0]");
const product = Chargebee.getInstance().getProduct(plan);
product.data["cf_grizzlee"] = grizzlee.checkoutReference();
});
</script>Chargebee and Stripe together
Chargebee often charges cards through a Stripe account. If that Stripe account is also connected to Grizzlee, both connections report the same payment, and partners could earn twice. Grizzlee prevents this when Chargebee names the Stripe payment. The payment_succeeded transaction carries the Stripe charge or PaymentIntent in id_at_gateway. A Chargebee order saves that ID, and a Stripe charge order saves its charge and PaymentIntent IDs. While a Chargebee connection is active, a new Stripe charge waits up to an hour for Chargebee to report it, because the Chargebee invoice holds the referral. If Chargebee does, the Stripe charge shows "already recorded this payment" in Incoming events. If Chargebee doesn't, the Stripe charge is recorded after the hour without the referral, and a later Chargebee report of it is ignored. Stripe payments outside Chargebee arrive up to an hour late. A Stripe refund of a payment the Chargebee order recorded is ignored, because Chargebee's credit notes adjust it.
Settings, then Connections, warns when both are connected. If that Stripe account takes no other payments, disable the Stripe connection. A payment Chargebee reports without a Stripe ID can still count twice. So can the earlier payments of an invoice paid in several Stripe payments, because Chargebee names only the payment that finishes it. Check new orders after you connect both.
Chargebee limits
- Orders must be in the company currency. An invoice in another currency waits in needs action.
- Chargebacks Chargebee records without a credit note do not reverse rewards. Record the refund in Grizzlee or through the API.
- Partner promo codes and email matching are not available for Chargebee.
Your own backend
A custom payment provider can call the HTTP API after your backend verifies payment. Send an order.completed event with a stable event ID, your order and customer IDs, the company currency, and the reward base. Report cumulative successful refunds with a new event ID.
PHP, Python and Ruby merchants can use their normal HTTP library. The Node SDK calls the same endpoints. Follow getting started for both SDK and cURL requests.
Outgoing webhooks
Add an HTTPS endpoint with New webhook. All events subscribes to every event type that exists when you save. Types added later reach the endpoint only after you edit it, because older SDK versions reject event types they do not know. To limit the endpoint, choose Choose events and select the events your backend handles. Save its signing secret on your server. Verify the raw request body and process each event ID once. Grizzlee retries failures up to eight attempts. View deliveries lists recent attempts and their last error.
Editing an endpoint keeps its signing secret. Removing an event keeps deliveries already queued for it. Queued deliveries go to the current URL. Disabling an endpoint stops queued deliveries when the worker picks them up, and enabling it again does not replay failed deliveries.
Provider signing secrets verify payment events sent into Grizzlee. Your Grizzlee signing secret verifies events sent out to your backend. Keep them separate. See webhooks for verification and an example of every event.
Failed provider events
Incoming events on the Events tab of Developers shows queued events, failures and their last error. Fix the connection or required metadata before retrying. Correct event mapping can repair missing customer or amount data while retaining the original event ID, order ID and action. A correction can create real rewards, so use amounts from your verified payment records.
Customer display names
Provider imports share no billing name with partners by default. To supply a name you intend partners to see, set grizzlee_customer_display_name on Stripe Charge metadata or the metadata used for a paid invoice. On the first invoice, metadata follows the same source priority as the customer ID. Later invoices with a saved customer mapping read the name from invoice metadata. For Shopify, send an order note attribute with that name. Values use the API's 1 to 80 character limit after trimming. Invalid values fail mapping and can be corrected before retrying.
Grizzlee stores the accepted name on the referral. The embedded dashboard shows it instead of a customer ID. Customer erasure removes it and ignores names on later provider reports. See Customer privacy.
Wallet company access
Set the optional server variable WALLET_COMPANIES to comma-separated company IDs on web and worker to limit Apple and Google Wallet passes to a test company while development credentials are in use. Unset it to allow all companies, or leave its value empty to allow none. The older name WALLET_WORKSPACES is read when WALLET_COMPANIES is unset; excluded companies show the existing previews and cannot download passes, get Google save links, create classes or objects, or queue or send wallet updates. Apple web service endpoints for passes already issued keep working, and AWS forwards the setting from grizzlee/runtime to both processes.
Apple Wallet
Apple Wallet is an optional Grizzlee server connection, shared by all companies. It stays unconnected until all four environment values exist. Web cards and the dashboard designer show a preview without signing when it is unconnected. Sandbox mode always uses previews. No merchant supplies a certificate or server key through the browser.
Create a Pass Type ID and its Pass Type certificate in the Apple Developer account. Export the certificate and its matching unencrypted private key as PEM files, then base64-encode each file into one line. Configure both the web process and worker:
| Variable | Value |
|---|---|
APPLE_WALLET_PASS_TYPE_ID | The certificate's Pass Type ID, such as pass.ca.grizzlee.loyalty |
APPLE_WALLET_TEAM_ID | The ten-character Apple developer Team ID |
APPLE_WALLET_CERTIFICATE | Base64-encoded PEM Pass Type certificate |
APPLE_WALLET_PRIVATE_KEY | Base64-encoded PEM private key, without a passphrase |
Keep these values server-only. The application checks the certificate's identifiers, validity dates and matching private key. Incomplete configuration leaves Wallet unconnected. Invalid complete configuration returns a setup error. APP_ORIGIN supplies the HTTPS pass web-service URL and private web-card links.
The public Apple WWDR G4 intermediate is included as a PEM string in packages/service/src/certificates/apple-wwdr-g4.ts, with its source URL in the file. It expires December 10, 2030. No WWDR environment value or network download is needed at runtime. Replace it when Apple's signing chain changes.
The worker needs outbound HTTPS with HTTP/2 to api.push.apple.com on port 443. Wallet uses this production host even with development Pass Type certificates. Pass updates use the same certificate and key as signing. Apple NFC is not enabled; passes use the existing member QR code.
First issuance permanently binds a pass to its Pass Type ID. Renewal under the same ID preserves updates. Changing the ID refuses regeneration and pushes for existing passes and logs the mismatch. Development passes cannot move to a different organization account. Issue new passes for that account instead. With no Apple certificate configured, live signing, device installation and APNs delivery still need verification after setup. See the loyalty protocol and storage guide.
Google Wallet
Google Wallet is an optional server connection shared by all companies. Set both values on the web and worker processes. Missing or incomplete configuration leaves it unconnected, and sandbox mode always shows a preview without calling Google. Invalid complete configuration returns a setup error.
| Variable | Value |
|---|---|
GOOGLE_WALLET_ISSUER_ID | Numeric issuer ID from the Google Pay & Wallet Console |
GOOGLE_WALLET_SERVICE_ACCOUNT | Base64-encoded service-account JSON key, on one line |
Create a Google Wallet issuer account, enable the Google Wallet API in its Google Cloud project, create a service account and download its JSON key. Add the service-account email as a developer in the issuer account. Keep the JSON key server-only. In AWS, add both values to grizzlee/runtime. Host activation forwards them to web and worker, like the Apple settings.
The service uses Node crypto to sign RS256 service-account assertions, exchanges them at https://oauth2.googleapis.com/token with the wallet_object.issuer scope, and caches access tokens until one minute before expiry. It also signs save JWTs with Node crypto. No Google SDK or auth dependency is needed for these two requests.
APP_ORIGIN must be the public HTTPS app origin. Google fetches logos and stamp PNGs from immutable, random URLs under /api/loyalty/images/. Permit outbound HTTPS to oauth2.googleapis.com, walletobjects.googleapis.com and pay.google.com. Do not require authentication on the image URLs or /api/wallet/google/callback. The callback verifies Google's root and intermediate signatures before changing records. No browser receives a service-account key.
New issuer accounts start in demo mode. Google marks passes [TEST ONLY], and only accounts in the issuer's test list can save them. Request publishing access in the Google Pay & Wallet Console and obtain Google's approval before customer launch. Classes initially use UNDER_REVIEW. See Google's web prerequisites.
The included English, Canadian French, Armenian and Russian button SVGs come from Google's official button assets. Google's brand guidelines describe sizing and market restrictions.
An issued object keeps its original issuer ID. Changing the configured issuer refuses saves and updates for those objects. Keep the same issuer when rotating service-account keys. There is no configured issuer account yet, so saving, callbacks and live updates still need verification with issuer test accounts after setup. See Google Wallet storage and delivery.
Grizzlee billing
Grizzlee charges companies for the software through Grizzlee's own Stripe account. This has nothing to do with the Stripe connections merchants add in Settings, and it uses its own key and webhook. Billing is off until all five variables below are set. With only some of them set, the worker stops at startup and live dashboard requests fail with a message that names the missing ones. Sample companies never bill.
| Variable | Value |
|---|---|
GRIZZLEE_BILLING_STRIPE_SECRET_KEY | Secret or restricted key, sk_live_ or rk_live_ |
GRIZZLEE_BILLING_STRIPE_WEBHOOK_SECRET | Signing secret of the billing webhook endpoint, whsec_ |
GRIZZLEE_BILLING_BASE_PRICE_ID | The flat monthly price, price_ |
GRIZZLEE_BILLING_USAGE_PRICE_ID | The metered price, price_ |
GRIZZLEE_BILLING_METER_EVENT_NAME | The meter's event name, for example grizzlee_billable_orders |
Set up Stripe in this order. Do it in a sandbox with test keys first, then repeat in live mode.
- Create a product named Grizzlee.
- Add a flat price to it: $29.00 USD, recurring, monthly. Its ID is
GRIZZLEE_BILLING_BASE_PRICE_ID. - Under Billing, then Meters, create a meter. Use the event name you chose, sum aggregation,
stripe_customer_idas the customer mapping key andvalueas the value key. Those two keys are Stripe's defaults. - Add a second price to the product: usage-based, USD, monthly, on that meter, with graduated tiers. The first tier covers 1 to 1,000 units at $0. The second covers 1,001 and up at $0.005 per unit. Its ID is
GRIZZLEE_BILLING_USAGE_PRICE_ID. Both prices must bill monthly, or Checkout refuses to combine them. - Under Developers, then Webhooks, add an endpoint at
https://app.grizzlee.ca/api/billing/stripeforcheckout.session.completed,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted,invoice.paidandinvoice.payment_failed. Its signing secret isGRIZZLEE_BILLING_STRIPE_WEBHOOK_SECRET. - In the Customer portal settings, activate the portal and allow customers to update payment methods, see invoices and cancel subscriptions. Set cancellation to take effect at the end of the billing period, because an immediate cancellation can leave the last metered orders uninvoiced. Leave plan switching off. Grizzlee sends the return address itself.
- Create a restricted key with write access to Customers, Checkout Sessions, Customer portal and Billing meter events, and read access to Subscriptions. It is
GRIZZLEE_BILLING_STRIPE_SECRET_KEY.
Owners and admins choose Subscribe in Settings, then Billing. Grizzlee creates one Stripe customer per company, with grizzlee_company_id metadata, and opens Checkout with both prices. Manage billing opens the Customer portal. Each webhook makes Grizzlee read the subscription again from Stripe, so retries and events that arrive out of order leave the latest status. Grizzlee ignores events for customers it did not create.
Every 10 minutes the worker counts each subscribed company's billable orders for its current Stripe period. It counts them the way the Billing tab does, but over the Stripe period instead of the calendar month. It sends Stripe only the orders it hasn't reported yet. The 1,000 included orders come from the price tiers, not from Grizzlee. Each meter event carries an identifier made of the company, the period start and the new total, and Grizzlee saves the count before sending it. A retry resends the same event, so Stripe counts it once. When a period ends, the worker sends its last orders dated inside that period. Stripe keeps the finished invoice as a draft for about an hour, which leaves time for them to count.
Grizzlee never blocks a company that hasn't paid. A failed payment shows on the Billing tab, and Stripe's retry settings decide what happens next.