In a hypothetical app called Report Mill, a team buys a pack of 100 report credits and the payment provider sends a notice to the Node.js API. The handler adds the credits to the team's account in Postgres and replies to the provider. If the response gets lost after the credits are saved, the provider can send the same notice again. A handler that adds 100 credits on each delivery will then give the team two packs for one purchase.
Store a receipt that identifies the purchase and make the receipt unique in Postgres. Save it in the same transaction as the credits so a crash can't leave one write saved without the other.
Name the purchase and the notice separately
Report Mill's payment notice is a webhook, a request one system sends to another when something changes. A provider such as Stripe can retry a failed delivery or send events in a different order from the one in which they happened. Stripe webhook guide
An event ID helps you trace delivery of a notice. To track the credit grant, use the ID of the purchase that paid for it. Two different event IDs can refer to the same purchase, so Report Mill needs to recognize both as a request to grant the same pack to the same workspace.
Choose the key to match the thing you grant. A key based only on the workspace would be too broad because it could block the team's next real purchase. A key based only on when the request arrived would allow every retry through.
Let the database enforce one grant
Store a receipt row with the workspace, purchase ID, and type of grant. Add a unique constraint for the purchase and grant type. A unique constraint tells Postgres to reject a second row with the same key, including when two handlers try at once.
Use one database transaction for inserting that receipt and adding the credits. A transaction saves its writes together or leaves them unsaved. If both writes succeed, the pack exists and the receipt proves why. If either fails, a retry can try the whole action again.
If you record the notice as processed and then try to add credits in a separate write, a crash between the writes can leave the buyer without their pack. Writing the credits first has the opposite problem. A crash before you record the receipt can cause the retry to add a second pack.
The handler should verify the source, find the stored payment-to-workspace link, and confirm that the payment permits a grant before opening this transaction. A missing link or failed database write should remain a failed attempt. It must not look like a finished purchase that needs no further work.
If two handlers try to insert the same receipt, Postgres allows only one to succeed. The other handler can return success once it verifies that the receipt covers the completed grant. Report other database errors so the work can be retried.
Save later work before returning
The email provider sends the receipt outside your Postgres transaction. To keep a crash from losing the email, save a pending email task in the same transaction as the receipt and grant. A supported worker or scheduled handler can read the task and send the email later. This pattern is often called an outbox.
Give the task its own stable key. A sender can crash after sending but before saving the result, so its retry rules still need to handle a possible second email. Use the email provider's duplicate controls where available and decide what a repeat means for your product.
On Forte, Services get no CPU between requests. A promise left running after the response or an in-process timer won't reliably send that mail. Actions beta can call a Service to process stored tasks. The work must finish inside the request, within 120 seconds. Read pending rows in bounded pages and keep progress in the database. Forte Actions
Long-running or persistent workers need a separate supported plan. Don't assume they can run as an idle Service process.
Test the points where the handler can stop
Start with a valid synthetic paid event. Test two deliveries in sequence and then test two deliveries at once. In each case, the balance should rise by 100 and the purchase should have one receipt.
Next, stop the handler after the database commit and before its response. Send the event again. The balance should stay at 100 because the first commit already saved the grant and receipt.
Use a second paid purchase to check that the key allows another valid pack. Then test an older payment notice after a refund or later state change. When the event alone can't safely establish current state, read trusted payment records before deciding what to do.
Save the observed rows and balances using safe IDs. These are planned checks until someone runs them. Stripe's fulfillment guidance also calls for repeat and concurrent calls to be safe. Stripe fulfillment guidance
Apply the rule to Forte payment triggers
Forte payment triggers target a Service and use a different source check from Stripe signatures. Validate X-Forte-Trusted on that route. Forte strips inbound X-Forte-* headers at its ingress before adding the trusted signal to its own calls. Keep the handler behind that boundary. A second direct path that trusts caller-supplied headers would weaken the check. Forte payment triggers
The documented policy allows five total attempts, 60 seconds apart, with a 30-second timeout for each request. Return success after the required work is safely stored, and provide a repair path if all attempts fail. A mixed checkout sends one completed-payment notice with relevant subscriptionIds, so read that shape when granting plans. Forte trigger retries, Checkout triggers
Build Report Mill's handler in a Sandbox Project and run the repeat and crash checks against the deployed Service. Use the payment guide to connect the trigger. Live payments require approved compliance registration.