Enterprise OnlyVersion: current

Falcon Payments Webhooks

Falcon Payments can handle 2 webhooks out of the box.

  • handlePaymentUpdate This is normally triggered by the payment provider when a payment is updated. Payment Provider -> Falcon Payments -> Shop.
  • handleOrderUpdate This is normally triggered by your shop and is used when an order is updated. Shop -> Falcon Payments -> Payment Provider.

Payment Updated Webhook

* Handles the update of a payment status
* @param provider The provider of the payment method
* @param ctx The koa context of the api call
* @returns An update payload that can be dispatched to the event emitter
handlePaymentUpdate(provider: string, ctx: Context): Promise<PaymentWebhookResult | void>;


This webhook sends data from your payment provider to your shop.

The basic flow is:

  1. Pass webhook URL to your payment provider when creating your order / payment intent.
  2. The payment provider triggers the webhook when a payment is changed within their system.
  3. The webhook triggers onPaymentUpdated in your payment provider package.
  4. This returns PaymentWebhookResult and causes the PAYMENT_STATUS_UPDATED event to be emitted.
  5. Your shop endpoints package listens for the PAYMENT_STATUS_UPDATED event.
  6. This then triggers a method in your shop API (onPaymentStatusUpdated in our core API packages).


An example use-case for this is if a payment status changes within the payment provider you may want this information to be passed to your shop.


The webhook URLs are configured in your server/config files.

"components": {
"payments": {
"package": "@deity/falcon-payments",
"config": {
"webhookBaseUrl": null,
"webhookUrl": "/falcon-payments/webhook"

In this case the webhook URL would be YOUR_URL/falcon-payments/webhook/PROVIDER e.g. https://deity-shop.io/falcon-payments/webhook/mollie

webhookBaseUrl should be pointed at your falcon server instance. If you're running your app on cloud your URLs will be proxied for you so your sites main domain is fine.

To configure this on a remote environement you can use the variable PAYMENT_WEBHOOK_BASE_URL.

Local Testing

The webhook URL needs to be accessible so you can't use localhost. we recommend using a service like ngrok on your Falcon server instance and setting the webhookBaseUrl to point to that. Once you've installed ngrok it should be as simple as running ngrok http 4000. Provided Falcon Server is running on port 4000 this will return you an https URL you can use.


  • provider - The payment provide code, e.g. mollie. This is used so we know which provider should handle the onPaymentUpdated.
  • context - This webhook comes from your payment provider so we pass the entire HTTP request context to the onPaymentUpdated method, such that query and body data can be extracted.


This method should return PaymentWebhookResult.

type PaymentWebhookResult = {
/** The id of the order that got updated */
orderId: string;
/** New payment status */
status: PaymentStatus;
/** Extra payload to be stored */
payload?: { [key: string]: any };
enum PaymentStatus {
open = 'open',
canceled = 'canceled',
pending = 'pending',
authorized = 'authorized',
expired = 'expired',
failed = 'failed',
paid = 'paid'


To subscribe to the PAYMENT_STATUS_UPDATED event emitted by your payment provider package you'll need to add this code to your shops endpoint package.

this.eventEmitter.on(PaymentEvents.PAYMENT_STATUS_UPDATED, async (payload: PaymentWebhookResult) => {
if (!this.ds.onPaymentStatusUpdated) {
await this.ds.onPaymentStatusUpdated(payload);

This example will trigger the onPaymentStatusUpdated in your shops API where the payload is PaymentWebhookResult.

Order Update Webhook

* Handles the update when a shop indicates the order to be updated
* @param payload The update payload
* @returns Whether the update was handled properly
handleOrderUpdate(payload: OrderWebhookResult): Promise<Boolean>;


This webhook sends data from your shop to your payment provider.

  1. The webhook URL is configured in Falcon Server
  2. The webhook URL is sent to your shop when placing an order
  3. The shop triggers the webhook when an order is updated (usually shipped, refunded or tracking_added).
  4. This triggers a method in your shop API (onOrderStatusUpdated)
  5. This triggers a method in your payment provider API (handleOrderUpdate)
  6. Depending on the status: OrderStatus provided different methods are triggered e.g. refund.


An example of this is if an order is marked as shipped in your shop, you may want to update it in your payment provider. Other examples include refunded orders and shipment tracking being added.



export type OrderWebhookResult = {
/** The provider of the payment method of the order that updated */
provider: string;
/** The the payment method of the order that updated */
method?: string;
/** The id of the order */
orderId: string;
/** New order status to be sent to the PSP */
status: OrderStatus;
/** A security code */
securityCode: string;
/** Any additional payload data */
payloadJson: string;
/** Specific order data that was changed */
additionalData?: OrderWebhookAdditional;
export type OrderWebhookAdditional = {
/** Shipping data of the update */
shippingData?: ShippingData;
/** The tracking data of the update */
trackingData?: ShipmentTracking;
/** The refund data of the update */
refundData?: RefundData;
export type ShippingData = {
items: ShippingItem[];
export type ShippingItem = {
id: string;
quantity?: number;
export type RefundData = {
transactionId?: string;
amount?: string;
currency: string;
adjustment?: string;
items?: RefundItem[];
export type RefundItem = {
type: RefundItemType;
amount?: string;
quantity?: number;
id?: string;
export enum RefundItemType {
product = 'product',
shipping = 'shipping',
surcharge = 'surcharge',
discount = 'discount'
export type ShipmentTracking = {
name: string;
number: string;
export enum OrderStatus {
shipped = 'shipped',
trackingAdded = 'tracking_added',
refund = 'refunded'


Boolean - depending on if the update was successful or not.

Ask the community. #help

If you can't find what you're looking for, the answer might be on our community slack channel. Our team keep a close eye on this and will usually get back to you within a few hours, if not straight away. If you haven't created an account yet please sign up here slack.deity.io.

Stay up to date

Do you want to be informed when we release new features or fixes? Sign up to our newsletter to stay in the loop.