Event Monitoring
Use WatchPayment for one intent and WatchAllPayments for cross-payment observers. Callbacks run in memory, not in a durable job queue. The sendConfirmationEmail and updateOrderStatus calls below are application placeholders; supply your own idempotent handlers and handle their errors.
Observe a payment
Register a watcher for an intent ID saved by your checkout code. This logs future events supplied by the configured implementation; it does not retrieve the current Stripe status or replay events that arrived before registration.
import { WatchPayment } from "@antelopejs/interface-stripe";
WatchPayment("pi_replace_with_your_test_intent", (orderId, intent) => {
console.log({ orderId, paymentIntentId: intent.id, status: intent.status });
});
Use WaitForPayment when you need to
retrieve the current status before waiting for a terminal event. Persist the
intent ID so your application can reconcile after a restart.
Keep callbacks short and handle errors inside them. The dispatcher calls them synchronously and does not await returned promises. A synchronous throw can interrupt later callbacks and terminal cleanup; asynchronous work needs its own rejection handling and recovery policy.
IntentChangeContext
The context object passed to every watcher callback. It carries metadata about the origin of the event.
interface IntentChangeContext {
local: boolean;
}
| Property | Type | Description |
|---|---|---|
local | boolean | true when the event was triggered by the current instance. false when it came from another cluster node. |
This distinction is useful in clustered deployments where an instance forwards
events to other instances. Filtering on context.local skips those forwarded
events, but does not deduplicate repeated webhook deliveries. Make side effects
such as sending emails or updating a database idempotent.
IntentWatcher Callback Type
All watcher functions accept a callback that conforms to the following signature:
type IntentWatcher = (
id: string | undefined,
intent: Stripe.PaymentIntent,
context: IntentChangeContext,
) => void;
| Parameter | Type | Description |
|---|---|---|
id | string | undefined | The custom payload ID from metadata.payload, set during InitializePayment. |
intent | Stripe.PaymentIntent | The full payment intent object with its current state. |
context | IntentChangeContext | Event origin metadata. |
WatchAllPayments
Registers a callback that fires whenever any payment intent changes status. Use this for global side effects such as logging, analytics, or audit trails.
import { WatchAllPayments } from "@antelopejs/interface-stripe";
WatchAllPayments((id, intent, context) => {
console.log(`Payment ${id} changed to status: ${intent.status}`);
console.log(`Origin: ${context.local ? "local" : "remote"}`);
if (intent.status === "succeeded") {
// Handle successful payment
} else if (intent.status === "canceled") {
// Handle canceled payment
}
});
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
callback | IntentWatcher | Yes | Function invoked on every payment intent change. |
onlyLocal | boolean | No | When true, the callback fires only for events originating locally. Defaults to false. |
Filtering Local Events
In a clustered deployment, set onlyLocal to true to skip events forwarded
from other instances. This is not an exactly-once delivery guarantee.
import { WatchAllPayments } from "@antelopejs/interface-stripe";
// Only react to events from the current instance
WatchAllPayments((id, intent, context) => {
if (intent.status === "succeeded") {
sendConfirmationEmail(id);
}
}, true);
Tip: The
onlyLocalfilter applies before the callback is invoked. When set totrue, events from remote cluster nodes never reach the callback.
WatchPayment
Registers a callback for a specific payment intent. The callback fires whenever that payment intent changes status. Once the intent reaches a terminal state (succeeded or canceled), the watcher is automatically removed.
import { WatchPayment } from "@antelopejs/interface-stripe";
WatchPayment("pi_1234567890", (id, intent, context) => {
if (intent.status === "succeeded") {
console.log(`Payment ${id} completed successfully`);
updateOrderStatus(id, "paid");
} else if (intent.status === "canceled") {
console.log(`Payment ${id} was canceled: ${intent.cancellation_reason}`);
updateOrderStatus(id, "canceled");
} else {
console.log(`Payment ${id} status: ${intent.status}`);
}
});
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
paymentIntentId | string | Yes | The Stripe payment intent ID to monitor. |
callback | IntentWatcher | Yes | Function invoked when the intent changes state. |
Note: Unlike
WatchAllPayments, theWatchPaymentcallback does not support theonlyLocalfilter. It fires for both local and remote events.
Automatic Cleanup
When a payment intent reaches a terminal state (succeeded or canceled), the event system performs automatic cleanup:
- Per-intent watchers registered with
WatchPaymentare removed. - Pending promises from
WaitForPaymentare resolved or rejected.
This cleanup applies to per-intent registrations after a terminal event is processed. Global watchers remain registered; neither watcher function returns an unsubscribe handle. Register global observers at application startup rather than once per request.
Combine Watchers with Payment Processing
The following example creates a payment intent, watches for its completion, and returns the client secret for frontend confirmation.
import { InitializePayment, WatchPayment } from "@antelopejs/interface-stripe";
async function processAndMonitorPayment(orderId: string, amount: number) {
const intent = await InitializePayment(orderId, {
amount,
currency: "usd",
payment_method_types: ["card"],
});
// Register without waiting, then let the browser confirm the payment.
WatchPayment(intent.id, (id, updatedIntent, context) => {
if (updatedIntent.status === "succeeded") {
console.log(`Order ${id} has been paid`);
} else if (updatedIntent.status === "canceled") {
console.log(`Payment for order ${id} was canceled`);
}
});
return {
clientSecret: intent.client_secret,
paymentIntentId: intent.id,
};
}
Watchers live in memory, so persist the intent ID and reconcile unfinished orders after a restart. See the separate reconciliation example.
Important: You can use
WatchPaymentandWaitForPaymenton the same payment intent. Do not await completion before returning the client secret. Both mechanisms are cleaned up automatically when the intent reaches a terminal state.