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.
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:
- A customer completes the checkout process and places an order.
- A new
order.created
event is initialized in Reflow. - A POST request is sent to an endpoint on the project's website
https://example.com/reflow-endpoint
. - The server receives the event. It contains all information about the placed order, including products, customer details, shipping, etc.
- Using that data, the project's backend code exports the new order in a PDF and sends it to the warehouse for fulfillment.
- 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.
- order.created
- order.updated
- user.created
- user.updated
- user.deleted
- products.changed
- categories.changed
{
"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"
}
{
"id": "test_evt_40637b556515ffb02286130b8555130b91a5ae11",
"object": "event",
"api-version": "2023-01-30",
"created": 1724412186,
"data": {
"object": {
"id": "12345678",
"object": "order",
"applied_gift_card": null,
"billing_address": null,
"created": 1724412186,
"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": "19e73cde35",
"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"
},
"previous_attributes": {
"fulfillment_status": "unfulfilled",
"payment_status": "pending"
}
},
"livemode": false,
"request_id": "test_req_eb524aff006a62d333972455acd8146a6baa4c45",
"type": "order.updated"
}
{
"id": "test_evt_52b2ffbd612d6a51187b4e701a2724ab94f00755",
"object": "event",
"api-version": "2023-01-30",
"created": 1724412193,
"data": {
"object": {
"id": "12345678",
"object": "user",
"created": 1724412193,
"email": "johndoe@example.com",
"livemode": false,
"meta": {
"address": {
"address": "123 Street Ave",
"city": "Paris",
"country": "FR",
"name": "John Doe",
"postcode": "1234"
},
"phone": "123456789"
},
"name": "John Doe",
"photo": "http://reflow.local/img/gravatar/default.png",
"provider": "google"
}
},
"livemode": false,
"request_id": "test_req_7ce07b2a1d62be991b7d4633cf699c567f1cd88e",
"type": "user.created"
}
{
"id": "test_evt_5dd0ad7e42468e7c07dcd21c5deaab3f2fffec29",
"object": "event",
"api-version": "2023-01-30",
"created": 1724412198,
"data": {
"object": {
"id": "12345678",
"object": "user",
"created": 1724412198,
"email": "johndoe@example.com",
"livemode": false,
"meta": {
"address": {
"address": "123 Street Ave",
"city": "Paris",
"country": "FR",
"name": "John Doe",
"postcode": "1234"
},
"phone": "123456789"
},
"name": "John Doe",
"photo": "http://reflow.local/img/gravatar/default.png",
"provider": "google"
},
"previous_attributes": {
"email": "old.email@example.com",
"name": "Old Name Value"
}
},
"livemode": false,
"request_id": "test_req_cd2cd803e0d8098c799cf1f92d83500881ed78c7",
"type": "user.updated"
}
{
"id": "test_evt_cee5e31e0b57fbbf43b6d556c1b78a5fc5ea0faf",
"object": "event",
"api-version": "2023-01-30",
"created": 1724412202,
"data": {
"object": {
"id": "12345678",
"object": "user",
"created": 1724412202,
"email": "johndoe@example.com",
"livemode": false,
"meta": {
"address": {
"address": "123 Street Ave",
"city": "Paris",
"country": "FR",
"name": "John Doe",
"postcode": "1234"
},
"phone": "123456789"
},
"name": "John Doe",
"photo": "http://reflow.local/img/gravatar/default.png",
"provider": "google"
}
},
"livemode": false,
"request_id": "test_req_9be3da3bedee116026eb396584dd57d340969471",
"type": "user.deleted"
}
{
"id": "test_evt_bce9b92550f2bfab516be973cd81499e28ac01ef",
"object": "event",
"api-version": "2023-01-30",
"created": 1724412207,
"data": {
"action": "update",
"items": [
"product_id_1",
"product_id_2",
"product_id_3"
]
},
"livemode": false,
"request_id": "test_req_437358176cac41bdf163050f0cff5b9be33595ac",
"type": "products.changed"
}
{
"id": "test_evt_b0ef1682c3374380f89825da47096985df9988dc",
"object": "event",
"api-version": "2023-01-30",
"created": 1724412211,
"data": {
"action": "update",
"items": [
"category_id_1",
"category_id_2",
"category_id_3"
]
},
"livemode": false,
"request_id": "test_req_a8a558ea5f919e6b896a630c3b7d5ef2ac72af8e",
"type": "categories.changed"
}
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 frompending
topaid
.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 });
}