Skip to main content

Webhooks

Webhooks allow you to receive real-time updates about events in your project. They open a lot of possibilities for extending and automating Reflow integrations.

PRO Feature

Webhooks are only available in the Reflow PRO plan. Subscribe to get access to webhooks, as well as many other features including digital products, email customization options, increased product limits, and more.

How Project Webhooks Work

Reflow uses webhooks to inform your application whenever an event occurs within your project. Events are sent in the form of an HTTP POST request to one or more URL endpoints on your server.

By monitoring these events, you can use the incoming data to perform custom backend operations specific to your project's use case. Here is a quick example:

  1. A customer completes the checkout process and places an order.
  2. A new order.created event is initialized in Reflow.
  3. A POST request is sent to an endpoint on the project's website https://example.com/reflow-endpoint.
  4. The server receives the event. It contains all information about the placed order, including products, customer details, shipping, etc.
  5. Using that data, the project's backend code exports the new order in a PDF and sends it to the warehouse for fulfillment.
  6. The server returns an HTTP 200 response to Reflow, marking the event as successfully received.

The Webhook Event Object

When Reflow sends a webhook request, it sends a JSON payload containing information about the event, such as the event type, the object that triggered the event, and any additional data associated with the event.

Below you can see examples for all the available events.

{
"id": "test_evt_b3a6be49ed03438394525398e24d0ffa59d96dca",
"object": "event",
"api-version": "2023-01-30",
"created": 1724412181,
"data": {
"object": {
"id": "12345678",
"object": "order",
"applied_gift_card": null,
"billing_address": null,
"created": 1724412181,
"currency": {
"code": "USD",
"name": "United States Dollar (USD)",
"zero_decimal": false
},
"customer": {
"email": "johndoe@example.com",
"ip": "10.11.12.13",
"name": "John Doe",
"phone": "123456789"
},
"custom_fields": null,
"delivery_method": "shipping",
"delivery_information": "Your UPS tracking number is 1Z12345E0291980793.",
"delivery_period": null,
"discounts": {
"amount_total": 0,
"amount_total_formatted": "$0.00",
"coupon": null
},
"fulfillment_status": "delivered",
"is_archived": false,
"is_canceled": false,
"line_items": [
{
"object": "line_item",
"has_discount": false,
"name": "T Shirt",
"personalization": {
"enabled": false,
"items": []
},
"price": 5000,
"price_formatted": "$50.00",
"product_id": "11111111",
"product_type": "physical",
"quantity": 1,
"sku": "sku_1",
"tax_amount": 0,
"tax_amount_formatted": "$0.00",
"unit_price": 5000,
"unit_price_formatted": "$50.00"
},
{
"object": "line_item",
"has_discount": false,
"name": "Jacket",
"personalization": {
"enabled": false,
"items": []
},
"price": 10000,
"price_formatted": "$100.00",
"product_id": "22222222",
"product_type": "physical",
"quantity": 2,
"sku": "sku_2",
"tax_amount": 0,
"tax_amount_formatted": "$0.00",
"unit_price": 5000,
"unit_price_formatted": "$50.00"
}
],
"livemode": false,
"note": "Order Note",
"payment_instructions": null,
"payment_method": "test-payment",
"payment_status": "paid",
"pickup_location": null,
"secure_hash": "cb38dee240",
"shipping_address": {
"address": "123 Street Ave",
"city": "Paris",
"country": "France",
"postcode": "1234"
},
"shipping_method": {
"name": "Worldwide Flat Rate",
"note": null,
"price": 0,
"price_formatted": "$0.00"
},
"taxes": {
"amount_total": 0,
"amount_total_formatted": "$0.00",
"regions": []
},
"total_amount": 15000,
"total_amount_formatted": "$150.00"
}
},
"livemode": false,
"request_id": "test_req_78a724c0094aab1a534e597a1b9ab6b6acf43e51",
"type": "order.created"
}
Note

The data->object property holds a JSON representation of the resource that triggered the event.

For *.updated events, the contents of data->object are in their state after the update.

The data->previous_attributes property holds the old values of the object, before the changes.

Webhook Event Types

Event types follow a naming pattern of resource.event. The webhook events we currently support are listed below.

  • order.created - Occurs when a new order has been placed.
  • order.updated - Occurs when an existing order has been updated. For example, when an order payment is confirmed its payment_status changes from pending to paid.
  • user.created - This event occurs when a customer creates an account via the Auth Registration feature.
  • user.updated - Occurs when a registered customer changes their user settings, e.g. their profile photo or email.
  • user.deleted - Occurs when a user account is removed from your Reflow project.
  • products.changed - Occurs when products are added, updated or deleted in the project.
  • categories.changed - Occurs when changes are made to the categories of your project.

In the future, we plan to expend the Webhooks API with more events, so stay tuned!

Examples

Capturing an Order Payment

In this example we will create a webhook endpoint that handles incoming orders. It will listen for the order.created and order.updated events to get all the order data and proceed with fulfillment once payment has been confirmed.

When a customer completes the shopping cart checkout process, the order.created event is triggered, indicating that a new order has been placed. This event contains all of the information about the purchased products, customer details, delivery, taxes, and more.

Initially, when an order is created, its payment_status is pending. After payment is confirmed, the payment_status of the order changes to paid. This status change is reflected by the order.updated event, which is asynchronous - the timing of the payment confirmation will depend on the used payment method.

function webhookHandler(Request $request)
{
$event = $request->getContent();

if ($event->type === 'order.created') {

// The project has received a new order.

// Save the order data to a local database.
$order = $event->data->object;
DB::saveOrder($order);
}

if ($event->type === 'order.updated') {

// An existing order has been updated.

// Get the order object and check the status.
$order = $event->data->object;

if ($order->payment_status === 'paid') {
// The order payment has been completed successfully.
// Proceed with fulfillment,
fulfillOrder($order);
}

if ($order->payment_status === 'failed') {
// There were multiple unsuccessful attempts to complete the payment.
// Contact the customer to assist them with their order.
contactCustomer($order->customer->email);
}
}

// Return a HTTP 200 response when we're done.
return response(['status' => 'success'], 200);
}

Content Revalidation

Another great use for webhooks is when developing with frameworks such as Next.js or Astro that support static static generation.

These frameworks can be configured to pre-render and cache content on the server for best performance. This comes with the drawback that once the cache is set, content can become stale - any updates to your project's products won't show up because the old version is cached.

The best way to get around this is to send webhooks to your static generated app when there are changes it should know about.

In this example, we've configured our Reflow project to send webhooks for the products.changed and categories.changed events. In our Next.js code, we will listen for these events and revalidate the server cache.

// Next.js example
// Webhook POST request are sent to `/api/revalidate` to control revalidation logic.

export async function revalidate(req: NextRequest): Promise<NextResponse> {
const body = await req.json();

const isCategoriesUpdate = body?.type == "categories.changed";
const isProductUpdate = body?.type == "products.changed";

// Revalidate the data for the respective cache tag.

if (isCategoriesUpdate) {
revalidateTag(TAGS.categories);
}

if (isProductUpdate) {
revalidateTag(TAGS.products);
}

return NextResponse.json({ cache_revalidated: true });
}