Set Up the Stripe Webhook
Set up the Ambassly Stripe webhook by adding your per-company endpoint in Stripe and selecting the required checkout, invoice, refund, and subscription events. Ambassly verifies Stripe signatures and processes events idempotently so retried webhooks do not create duplicate referrals or commissions.
Ambassly receives subscription and refund events from your Stripe account through a per-company webhook endpoint.
Your endpoint URL is:
https://ambassly.com/api/hook/COMPANY_PUBLIC_ID
Replace COMPANY_PUBLIC_ID with the company public ID shown in Ambassly.
Add the endpoint in Stripe
- Open the Stripe Dashboard for the account that processes your customer payments.
- Go to Developers -> Webhooks.
- Click Add endpoint.
- Enter your Ambassly endpoint URL:
https://ambassly.com/api/hook/COMPANY_PUBLIC_ID
- Select these four events exactly:
checkout.session.completed
invoice.paid
charge.refunded
customer.subscription.deleted
- Save the endpoint.
- Open the endpoint, reveal the signing secret, and save that
whsec_...value in Ambassly for this company.
Use the signing secret from this Dashboard webhook endpoint. Do not use a Stripe CLI signing secret for the production Dashboard endpoint.
Why these events are required
checkout.session.completed creates the Ambassly referral. Ambassly resolves the referral code from the Checkout Session's client_reference_id or metadata.ambassly, then links the referral to the Stripe customer.
invoice.paid creates a pending commission when the Stripe customer maps to a referral and the invoice is within the program's commission window: the first paid invoice for a one-time program, or each invoice within the recurring window (up to recurring_months, or every invoice when the program is lifetime) for a recurring program.
charge.refunded claws back matching pending or approved commissions when the charge is refunded.
customer.subscription.deleted claws back matching pending or approved commissions when the subscription is deleted.
Signature verification
Ambassly verifies each request with Stripe's webhook signature. Stripe sends the signature in the Stripe-Signature header, and Ambassly verifies it against the per-company webhook signing secret you saved.
The verification model is:
const event = stripe.webhooks.constructEvent(
rawRequestBody,
stripeSignatureHeader,
companyWebhookSecret
);
The request body must be the raw body Stripe sent. If the body is parsed or changed before verification, signature verification can fail.
If Ambassly returns a signature error during setup, check:
- The endpoint URL uses the correct
COMPANY_PUBLIC_ID. - The saved secret starts with
whsec_. - The secret came from the same Stripe Dashboard webhook endpoint that is sending events.
- Test-mode events are sent to a test-mode endpoint secret, and live-mode events are sent to the live-mode endpoint secret.
Idempotency guarantees
Stripe may deliver the same event more than once. Ambassly treats webhook processing as idempotent.
For every incoming Stripe webhook, Ambassly first inserts a row into its webhook_events ledger using Stripe's event ID as a unique key:
webhook_events.stripe_event_id
If the Stripe event ID has already been seen, Ambassly skips processing it.
This means:
- Sending the same
checkout.session.completedtwice does not create two referrals. - Sending the same
invoice.paidtwice does not create two commissions. - Sending the same
charge.refundedtwice does not claw back twice. - Sending the same
customer.subscription.deletedtwice does not claw back twice.
Ambassly's idempotency is scoped to Stripe event IDs. A different Stripe event with a different event ID is processed independently.
Testing
Use Stripe test mode first.
- Create a test checkout with an Ambassly code in
client_reference_idand/ormetadata.ambassly. - Complete the checkout.
- Confirm Stripe delivered
checkout.session.completedto:
https://ambassly.com/api/hook/COMPANY_PUBLIC_ID
- For recurring products, confirm Stripe later sends
invoice.paid. - Refund the charge in test mode and confirm Stripe sends
charge.refunded. - Cancel the test subscription and confirm Stripe sends
customer.subscription.deleted.