Skip to content

Instantly share code, notes, and snippets.

@jessepearson
Last active October 30, 2024 23:31
Show Gist options
  • Save jessepearson/66a0e72706b99c15b52dee7ce59e1d31 to your computer and use it in GitHub Desktop.
Save jessepearson/66a0e72706b99c15b52dee7ce59e1d31 to your computer and use it in GitHub Desktop.
How to add a new custom Webhook topic in WooCommerce, with example of order filtering.
<?php // do not copy this line
/**
* add_new_topic_hooks will add a new webhook topic hook.
* @param array $topic_hooks Esxisting topic hooks.
*/
function add_new_topic_hooks( $topic_hooks ) {
// Array that has the topic as resource.event with arrays of actions that call that topic.
$new_hooks = array(
'order.custom_filter' => array(
'custom_order_filter',
),
);
return array_merge( $topic_hooks, $new_hooks );
}
add_filter( 'woocommerce_webhook_topic_hooks', 'add_new_topic_hooks' );
/**
* add_new_topic_events will add new events for topic resources.
* @param array $topic_events Existing valid events for resources.
*/
function add_new_topic_events( $topic_events ) {
// New events to be used for resources.
$new_events = array(
'custom_filter',
);
return array_merge( $topic_events, $new_events );
}
add_filter( 'woocommerce_valid_webhook_events', 'add_new_topic_events' );
/**
* add_new_webhook_topics adds the new webhook to the dropdown list on the Webhook page.
* @param array $topics Array of topics with the i18n proper name.
*/
function add_new_webhook_topics( $topics ) {
// New topic array to add to the list, must match hooks being created.
$new_topics = array(
'order.custom_filter' => __( 'Order Custom Filter', 'woocommerce' ),
);
return array_merge( $topics, $new_topics );
}
add_filter( 'woocommerce_webhook_topics', 'add_new_webhook_topics' );
/**
* my_order_item_check will check an order when it is created through the checkout form,
* if it has product ID 10603 as one of the items, it will fire off the action `custom_order_filter`
*
* @param int $order_id The ID of the order that was just created.
* @param array $posted_data Array of all of the data that was posted through checkout form.
* @param object $order The order object.
* @return null
*/
function my_order_item_check( $order_id, $posted_data, $order ) {
$order = wc_get_order( $order_id );
$items = $order->get_items();
foreach ( $items as $item ) {
if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
if ( 10603 === $item->get_product_id() ) {
do_action( 'custom_order_filter', $order_id, $posted_data, $order );
return;
}
}
}
}
add_action( 'woocommerce_checkout_order_processed', 'my_order_item_check', 10, 3 );
/**
* The two below actions are what the order.created webhook is tied into, it is up to you to use these if you wish.
*/
// add_action( 'woocommerce_process_shop_order_meta', 'my_order_item_check' );
// add_action( 'woocommerce_api_create_order', 'my_order_item_check' );
@jaxne
Copy link

jaxne commented Nov 10, 2017

Thanks for posting this. We need to send the order payload that the Order Created action does now, but only when payment is complete. Can I use this somehow to add a "Payment Complete" action option on the webhook actions dropdown form that is tied to the woocommerce_payment_complete action trigger and send the order payload at that time?

We ran into a scenario that if a customer tries to checkout but the payment fails (i.e. card declined or something else) the failed order is still created and the webhook is fired with the status of "failure". If during this transaction the customer fixes the credit card or issue with payment and the payment is successful, the order is moved to processing BUT the webhook will not resend the payload since the order was already created. That is why we'd like to see if we can tie the order payload being sent when the woocommerce_payment_complete is triggered, i.e. after the payment has gone through and is successful. Any tips or guidance would be appreciated.

Thank you!

@jtisler
Copy link

jtisler commented Nov 16, 2017

@jaxne why don't you ignore all requests that are not completed on API side?

@robberezowski
Copy link

Hi. Been searching high and low for examples on how to create custom webhooks in woocommerce and this looks pretty close to what I'm looking for with a few tweaks. The problem is, I'm a bit of a noob. Where do you put the code for this? Do you drop it in the theme functions.php? A custom file somewhere? I'm not sure how this all gets called.

Thanks!

@martezhubbard
Copy link

Yes I'm with jaxne and robberezowski. I need what Jaxne said but I'm a noob as well and I'm not sure how to actually implement this.

@chris-schertenlieb
Copy link

chris-schertenlieb commented May 24, 2019

@jaxne @martezhubbard @robberezowski

Hopefully you're not still struggling with this, but yes, this code would go into the functions.php file in your child theme.

EDIT: you can also download a plugin called "Code Snippets" which gives you a nice UI for adding things to the functions.php file.

@jaxne
Copy link

jaxne commented May 24, 2019

@chris-schertenlieb Thanks for posting back.

@jtisler - We are ignoring the failed order payloads being sent to our endpoint. The issue is if the customer fixes the failed order after getting an error message (e.g. they fat fingered their credit card number or something), the webhook doesn't fire again and we don't get a "good" order payload resent since the order was already created. That's why we wanted to set up a trigger for only when the payment is complete and all is good.

We're planning on circling back to this and do some tests with again (great rainy Memorial Day weekend here) and see if we can create on for Payment Complete status.

@anderskitson
Copy link

Hi,

I have the following code that is giving me this error when I try and pay with stripe credit card, if I get rid of the add_action it runs so woocommerce is working fine. Here is the error
Failed to load resource: the server responded with a status of 500 ()
and here is the code


/**
 * add_new_topic_hooks will add a new webhook topic hook. 
 * @param array $topic_hooks Esxisting topic hooks.
 */
function add_new_topic_hooks( $topic_hooks ) {
	// Array that has the topic as resource.event with arrays of actions that call that topic.
	$new_hooks = array(
		'order.custom_filter' => array(
			'custom_order_filter',
			),
		);
	return array_merge( $topic_hooks, $new_hooks );
}
add_filter( 'woocommerce_webhook_topic_hooks', 'add_new_topic_hooks' );
/**
 * add_new_topic_events will add new events for topic resources.
 * @param array $topic_events Existing valid events for resources.
 */
function add_new_topic_events( $topic_events ) {
	// New events to be used for resources.
	$new_events = array(
		'custom_filter',
		); 
	return array_merge( $topic_events, $new_events );
}
add_filter( 'woocommerce_valid_webhook_events', 'add_new_topic_events' );
/**
 * add_new_webhook_topics adds the new webhook to the dropdown list on the Webhook page.
 * @param array $topics Array of topics with the i18n proper name.
 */
function add_new_webhook_topics( $topics ) {
	// New topic array to add to the list, must match hooks being created.
	$new_topics = array( 
		'order.custom_filter' => __( 'Order Custom Filter', 'woocommerce' ),
		);
	return array_merge( $topics, $new_topics );
}
add_filter( 'woocommerce_webhook_topics', 'add_new_webhook_topics' );
/**
 * my_order_item_check will check an order when it is created through the checkout form,
 * if it has product ID 10603 as one of the items, it will fire off the action `custom_order_filter`
 * 
 * @param  int    $order_id    The ID of the order that was just created.
 * @param  array  $posted_data Array of all of the data that was posted through checkout form.
 * @param  object $order       The order object.
 * @return null
 */
function my_order_item_check( $order_id, $posted_data, $order ) {

  error_log("please work");
	
	$order = wc_get_order( $order_id );
	$items = $order->get_items();
	foreach ( $items as $item ) {
		
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
			
			if ( 1457 === $item->get_product_id() ) {
				
				do_action( 'custom_order_filter', $order_id, $posted_data, $order );
				return;
			}
		}
	}
}
add_action( 'woocommerce_payment_complete', 'my_order_item_check', 10, 3 );
/**
 *  The two below actions are what the order.created webhook is tied into, it is up to you to use these if you wish. 
 */
// add_action( 'woocommerce_process_shop_order_meta', 'my_order_item_check' );
// add_action( 'woocommerce_api_create_order', 'my_order_item_check' );

@OpsAndresRosales
Copy link

OpsAndresRosales commented Feb 20, 2020

Hey there,

I'm trying to use this code to create a webhook for the existing WooCommerce Subscriptions event woocommerce_subscription_status_cancelled.

Here is the code I have as of right now:

 * add_new_topic_hooks will add a new webhook topic. 
 * @param array $topic_hooks Existing topic hooks.
 */
function add_new_topic_hooks( $topic_hooks ) {

	// Array that has the topic as resource.event with arrays of actions that call that topic.
	$new_hooks = array(
		'subscription.custom_cancel' => array(
			'woocommerce_subscription_status_cancelled',
			),
		);

	return array_merge( $topic_hooks, $new_hooks );
}
add_filter( 'woocommerce_webhook_topic_hooks', 'add_new_topic_hooks' );

/**
 * add_new_topic_events will add new events for topic resources.
 * @param array $topic_events Existing valid events for resources.
 */
function add_new_topic_events( $topic_events ) {

	// New events to be used for resources.
	$new_events = array(
		'custom_cancel',
		);

	return array_merge( $topic_events, $new_events );
}
add_filter( 'woocommerce_valid_webhook_events', 'add_new_topic_events' );

/**
 * add_new_webhook_topics adds the new webhook to the dropdown list on the Webhook page.
 * @param array $topics Array of topics with the i18n proper name.
 */
function add_new_webhook_topics( $topics ) {

	// New topic array to add to the list, must match hooks being created.
	$new_topics = array( 
		'subscription.custom_cancel' => __( 'Subscription Cancelled', 'woocommerce' ),
		);

	return array_merge( $topics, $new_topics );
}
add_filter( 'woocommerce_webhook_topics', 'add_new_webhook_topics' );

I have the new webhook appearing in the dropdown, but it doesn't appear to be triggered when I cancel a subscription.

In looking at your code, it appears that you are creating a webhook for a new action that you are creating yourself; how should I modify this to create a new Webhook for an existing action that doesn't already have a webhook?

Also, I don't see where you're declaring the custom_order_filter function in the attached code. Will the call to do_action pass those params to the webhook, or is custom_order_filter declared elsewhere?

Thanks!
Andres

@dhirenpatel22
Copy link

Hey there,

I'm trying to use this code to create a webhook for the existing WooCommerce Subscriptions event woocommerce_subscription_status_cancelled.

Here is the code I have as of right now:

 * add_new_topic_hooks will add a new webhook topic. 
 * @param array $topic_hooks Existing topic hooks.
 */
function add_new_topic_hooks( $topic_hooks ) {

	// Array that has the topic as resource.event with arrays of actions that call that topic.
	$new_hooks = array(
		'subscription.custom_cancel' => array(
			'woocommerce_subscription_status_cancelled',
			),
		);

	return array_merge( $topic_hooks, $new_hooks );
}
add_filter( 'woocommerce_webhook_topic_hooks', 'add_new_topic_hooks' );

/**
 * add_new_topic_events will add new events for topic resources.
 * @param array $topic_events Existing valid events for resources.
 */
function add_new_topic_events( $topic_events ) {

	// New events to be used for resources.
	$new_events = array(
		'custom_cancel',
		);

	return array_merge( $topic_events, $new_events );
}
add_filter( 'woocommerce_valid_webhook_events', 'add_new_topic_events' );

/**
 * add_new_webhook_topics adds the new webhook to the dropdown list on the Webhook page.
 * @param array $topics Array of topics with the i18n proper name.
 */
function add_new_webhook_topics( $topics ) {

	// New topic array to add to the list, must match hooks being created.
	$new_topics = array( 
		'subscription.custom_cancel' => __( 'Subscription Cancelled', 'woocommerce' ),
		);

	return array_merge( $topics, $new_topics );
}
add_filter( 'woocommerce_webhook_topics', 'add_new_webhook_topics' );

I have the new webhook appearing in the dropdown, but it doesn't appear to be triggered when I cancel a subscription.

In looking at your code, it appears that you are creating a webhook for a new action that you are creating yourself; how should I modify this to create a new Webhook for an existing action that doesn't already have a webhook?

Also, I don't see where you're declaring the custom_order_filter function in the attached code. Will the call to do_action pass those params to the webhook, or is custom_order_filter declared elsewhere?

Thanks!
Andres

Hey Andres,

I am looking for a similar solution but had the same issue. Have you found any solution for this?

@OpsAndresRosales
Copy link

Hey Andres,

I am looking for a similar solution but had the same issue. Have you found any solution for this?

Yes I did! I installed the Snippets plugin for WordPress and created a new snippet with the following code to create custom hooks for Subscription status change to cancelled and subscription status change to active:

function add_custom_filters_and_actions() {

	add_filter( 'woocommerce_webhook_topic_hooks', 'add_custom_wcs_topics', 30, 2 );

	add_filter( 'woocommerce_valid_webhook_events', 'add_custom_wcs_events', 20, 1 );

	add_filter( 'woocommerce_webhook_topics' , 'add_custom_wcs_topics_admin_menu', 20, 1 );
	
	add_action( 'woocommerce_subscription_status_pending-cancel_to_cancelled', 'add_subscription_cancelled_callback', 10, 1 );
	
	add_action( 'woocommerce_subscription_status_pending_to_active', 'add_subscription_active_callback', 10, 1 );
}

/**
 * Add Custom Subscription webhook topics
 */
function add_custom_wcs_topics( $topic_hooks, $webhook ) {
	
	switch ( $webhook->get_resource() ) {
		case 'subscription':
			$topic_hooks = apply_filters( 'woocommerce_subscriptions_webhook_topics', array(
				'subscription.cancelled' => array(
					'wcs_webhook_status_cancelled'
				),
				'subscription.activated' => array(
					'wcs_webhook_status_active'
				)
			), $webhook );
			break;
	}

	return $topic_hooks;
}

/**
 * Add Subscription topics to the Webhooks dropdown menu in when creating a new webhook.
 */
function add_custom_wcs_topics_admin_menu( $topics ) {

	$front_end_topics = array(
		'subscription.cancelled'  => __( 'Subscription Cancelled', 'woocommerce-subscriptions' ),
		'subscription.activated'  => __( 'Subscription Activated', 'woocommerce-subscriptions' )
	);

	return array_merge( $topics, $front_end_topics );
}

/**
 * Add webhook event for subscription switched.
 */
function add_custom_wcs_events( $events ) {
	$events[] = 'cancelled';
	$events[] = 'activated';

	return $events;
}

function add_subscription_cancelled_callback( $subscription ) {
	do_action( 'wcs_webhook_status_cancelled', $subscription->get_id());
}

function add_subscription_active_callback( $subscription ) {
	do_action( 'wcs_webhook_status_active', $subscription->get_id());
}

add_custom_filters_and_actions();

@rickysullivan
Copy link

@chris-schertenlieb Thanks for posting back.

@jtisler - We are ignoring the failed order payloads being sent to our endpoint. The issue is if the customer fixes the failed order after getting an error message (e.g. they fat fingered their credit card number or something), the webhook doesn't fire again and we don't get a "good" order payload resent since the order was already created. That's why we wanted to set up a trigger for only when the payment is complete and all is good.

We're planning on circling back to this and do some tests with again (great rainy Memorial Day weekend here) and see if we can create on for Payment Complete status.

@jaxne Did you find a solution? I've got a similar issue. I was using the order created webhook to notify our accounting software of new orders but because the customers card was declined, the webhook only fired once. At the other end of the process I check to see if the order status is "processing" before adding the order details to our accounting system. I've created a custom webhook, it fires when I want it to, but it's not sending any payload.

"bodyRaw": "[]"

@pfriedl
Copy link

pfriedl commented Apr 9, 2021

I'm selling licenses to a web app, and managing licenses in a separate table. So I need to perform a webhook on these events:

  • subscription created at checkout
  • subscription renewal payment failed
  • subscription canceled
  • subscription reactivated

I also need to send a payload of the order ID, billing data (name, email), subscription ID and SKU. Can somebody help with this? Willing to pay for some quick work!

@Meloman-zz
Copy link

Hi all,

Anybody can help me make it for order.cancelled ?
https://stackoverflow.com/questions/68556469/woocommerce-webhook-for-canceled-order-not-fired

Thanks.

@onetarek
Copy link

Thanks for posting this. We need to send the order payload that the Order Created action does now, but only when payment is complete. Can I use this somehow to add a "Payment Complete" action option on the webhook actions dropdown form that is tied to the woocommerce_payment_complete action trigger and send the order payload at that time?

We ran into a scenario that if a customer tries to checkout but the payment fails (i.e. card declined or something else) the failed order is still created and the webhook is fired with the status of "failure". If during this transaction the customer fixes the credit card or issue with payment and the payment is successful, the order is moved to processing BUT the webhook will not resend the payload since the order was already created. That is why we'd like to see if we can tie the order payload being sent when the woocommerce_payment_complete is triggered, i.e. after the payment has gone through and is successful. Any tips or guidance would be appreciated.

Thank you!

I needed to send webhook only on order completed. I solved it in my way.
Here is my gist https://gist.github.com/onetarek/6652c0ceb04c81918b78dca5e495c49a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment