My solution
1
Your previous attempts to implement this project via Stape and GTM failed because, regardless of whether Stape and GTM are used for this project, its implementation inevitably requires proper programming, and Arthur and Anirudh are clearly incompetent at it.
2
Your new description shows that you are on your way to realizing this; previously you did not even deem it necessary to mention the key technologies of your SaaS, and even now, it would have been worth indicating that the SaaS uses not just React, but Next.js.
3
For this project, Stape is redundant, and GTM is orthogonal to the project's goal (GTM can be useful for other SaaS functions).
4
Below (§§5-11) is the best architecture for your case.
5
fbp, fbc and other tracking data (the IP address, the User-Agent HTTP header) are best stored in the Django database, because, to increase Event Match Quality, they must be combined with the customer's data already stored there; therefore, storing them on an external service like Stape is pointless.
6
The SaaS frontend must disable the automatic sending of events to Meta before initializing the Meta Pixel, and instead:
6.1
Generate a unique event_id (UUIDv4) for each event instance being tracked and associate it with this instance.
6.2
Call fbq('<method>', '<event name>', {<event data>}, {eventID: event_id}), where <method> is track for standard Meta events (PageView, StartTrial) and trackCustom for custom events (CancelTrial, CancelSubscription).
6.3
Read the _fbp and _fbc cookie values, if present (_fbc is present only if the visitor has followed a link with the fbclid parameter, and _fbp may be absent due to browser blockers).
6.4
Send a POST request to a custom Django endpoint (e.g., /api/tracking/event/) with a JSON body containing the event name, the event_id, the event data, the _fbp and _fbc cookie values (if any), the page URL, and the customer ID (if the visitor is authenticated).
7
In Django, create the §6.4 endpoint to receive the events, store their tracking data in the database (§5), enrich the events, and send them to Meta.
8
Your dichotomy between «choosing when browser events should fire» and «choosing when server events should fire» is false; you are close to understanding this yourself because it contradicts your correct statement a few paragraphs above: «events should be sent from both the browser and the server».
I will state the rationale explicitly:
8.1
In the US, a third of frontend tracking data do not reach Meta due to built-in browser protections (Safari ITP, Firefox ETP, Brave Shields), visitors' refusal of cookies via cookie banners, and browser extensions like AdBlock and uBlock.
By the way, this clearly corresponds to your statement from the previous project description: «right now it is tracking around 70% of the data».
8.2
Sending some events to Meta only from the server is also an evil that should be avoided wherever possible (for Stripe webhooks, though, this cannot be avoided), because the Meta Pixel on the frontend does a lot of things that are impossible to replicate on the backend: it collects page microdata, tracks mouse behavior patterns, and collects a mass of session metadata, which are important for targeting algorithms.
9
To process the Stripe events for Meta, Django must:
9.1
Listen for the invoice.payment_succeeded and invoice.payment_failed events via webhooks.
9.2
For invoice.payment_succeeded, distinguish 2 cases:
9.2.1
If the payment is the initial charge for a «Premium Plus» subscription without a trial period → ignore this webhook, as the direct Subscribe event is processed in real time during the active browser session via the §§6–7 flow.
9.2.2
If the payment occurs after the trial period ends (determined from the invoice/subscription data) → treat it as a trial-to-paid Subscribe conversion.
9.3
Identify the customer in the database by the customer parameter from the webhook.
9.4
Use the Stripe event ID as the event_id: unlike in §6.1, generating a unique event_id is wrong because Stripe can send the same notification multiple times.
9.5
Enrich the event data for Meta with the event_id and the §5 data.
9.6
Send the corresponding Subscribe or PaymentFailed event to the Meta Conversions API.
10
In §7 and §9 Django must hash the customer's personal data (email, phone, name, etc.) using SHA-256: without this, the Meta Conversions API will return an error.
11
The CancelTrial and CancelSubscription events must be processed in 2 ways:
11.1
According to the §§6-7 flow, when the customer terminates the trial or subscription by their own action through the UI.
11.2
Similarly to §9, when the trial or subscription is terminated without any explicit action by the customer (in particular, when the subscription is canceled because payment attempts fail after the trial or the current subscription period expires).