diff --git a/README.md b/README.md
index 25b1787..f9fee6e 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ The plugin provides an SDK client to easily integrate with Omnisend API.
 > To use in your plugin you must check if wp-omnisend plugin is installed.
 > Provided client will send data to Omnisend if it is connected.
 
-You can find function references in the [client folder](https://github.com/omnisend/wp-omnisend/tree/main/omnisend/includes/Public/V1).
+You can find function references in the [client folder](https://github.com/omnisend/wp-omnisend/tree/main/omnisend/includes/SDK/V1).
 
 ### Examples
 
@@ -46,33 +46,54 @@ This is done by getting an actual client
 Here is how you can create a basic client & submit contact.
 
 ```php
-		$contact  = new Contact();
-
-		$contact->set_email( $email );
-		if ( $phone_number != '' ) {
-			$contact->set_phone( $phone_number );
-		}
-		$contact->set_first_name( $first_name );
-		$contact->set_last_name( $last_name );
-		$contact->set_birthday( $birthday );
-		$contact->set_postal_code( $postal_code );
-		$contact->set_address( $address );
-		$contact->set_state( $state );
-		$contact->set_country( $country );
-		$contact->set_city( $city );
-		if ( $email_consent ) {
-			$contact->set_email_consent( 'actual_email_consent_for_gdrp' );
-			$contact->set_email_opt_in( 'where user opted to become subscriber' );
-		}
-
-        $client = \Omnisend\SDK\V1\Omnisend::get_client( 'integration name', 'integration version' );
-
-        $response = $client->create_contact( $contact );
+$contact  = new Contact();
+
+$contact->set_email( $email );
+if ( $phone_number != '' ) {
+	$contact->set_phone( $phone_number );
+}
+$contact->set_first_name( $first_name );
+$contact->set_last_name( $last_name );
+$contact->set_birthday( $birthday );
+$contact->set_postal_code( $postal_code );
+$contact->set_address( $address );
+$contact->set_state( $state );
+$contact->set_country( $country );
+$contact->set_city( $city );
+if ( $email_consent ) {
+	$contact->set_email_consent( 'actual_email_consent_for_gdrp' );
+	$contact->set_email_opt_in( 'where user opted to become subscriber' );
+}
+$client = \Omnisend\SDK\V1\Omnisend::get_client( 'integration name', 'integration version' );
+	
+$response = $client->create_contact( $contact );
 ```
 
+#### Customer events
+
+Here is how you can send customer events.
+
+```php
+$contact  = new Contact();
+$contact->set_email( $email );
+
+$event =  new Event();
+$event->set_contact( $contact );
+$event->set_origin( 'api' );
+$event->set_event_name( 'something hapened' );
+$event->add_properties( 'pageUrl', $pageUrl );
+$event->add_properties( 'pageTitle', $pageTitle );
+
+$client = \Omnisend\SDK\V1\Omnisend::get_client( 'integration name', 'integration version' );
+
+$response = $client->send_customer_event($event);
+```
+
+You can send contact identifiers and if contact exists, then event will be attributed for this contact, if not - new contact will be created and event will be attributed to this new contact
+
 #### Error handling
 
-If data provided is invalid or creation fails, then
+If data provided is invalid or contact creation fails, then
 
 ```php
 $response = $client->create_contact($contact)
@@ -87,6 +108,21 @@ Will return `CreateContactResponse`. Depending on your integration logic you sho
     }
 ```
 
+If data provided is invalid or sending customer event fails, then
+
+```php
+$response = $client->send_customer_event($event);
+```
+
+Will return `SendCustomerEventResponse`. Depending on your integration logic you should handle the error i.e
+
+```php
+    if ( $response->get_wp_error()->has_errors() ) {
+        error_log( 'Error in after_submission: ' . $response->get_wp_error()->get_error_message());
+        return;
+    }
+```
+
 ## PHP Linting
 
 WordPress.org team mandates our plugin to be linted
diff --git a/omnisend/class-omnisend-core-bootstrap.php b/omnisend/class-omnisend-core-bootstrap.php
index dc092b6..237b5c3 100644
--- a/omnisend/class-omnisend-core-bootstrap.php
+++ b/omnisend/class-omnisend-core-bootstrap.php
@@ -34,6 +34,7 @@
 
 // Change for different environment.
 const OMNISEND_CORE_API_V3      = 'https://api.omnisend.com/v3';
+const OMNISEND_CORE_API_V5      = 'https://api.omnisend.com/v5';
 const OMNISEND_CORE_SNIPPET_URL = 'https://omnisnippet1.com/inshop/launcher-v2.js';
 
 // Omnisend for Woo plugin.
diff --git a/omnisend/includes/Internal/V1/class-client.php b/omnisend/includes/Internal/V1/class-client.php
index a3b9833..1937251 100644
--- a/omnisend/includes/Internal/V1/class-client.php
+++ b/omnisend/includes/Internal/V1/class-client.php
@@ -9,12 +9,17 @@
 
 use Omnisend\SDK\V1\Contact;
 use Omnisend\SDK\V1\CreateContactResponse;
+use Omnisend\SDK\V1\Event;
+use Omnisend\SDK\V1\SendCustomerEventResponse;
 use WP_Error;
 
 defined( 'ABSPATH' ) || die( 'no direct access' );
 
 class Client implements \Omnisend\SDK\V1\Client {
 
+
+
+
 	private string $api_key;
 	private string $plugin_name;
 	private string $plugin_version;
@@ -47,7 +52,7 @@ public function create_contact( $contact ): CreateContactResponse {
 		}
 
 		$response = wp_remote_post(
-			OMNISEND_CORE_API_V3 . '/contacts',
+			OMNISEND_CORE_API_V5 . '/contacts',
 			array(
 				'body'    => wp_json_encode( $contact->to_array() ),
 				'headers' => array(
@@ -89,6 +94,50 @@ public function create_contact( $contact ): CreateContactResponse {
 		return new CreateContactResponse( (string) $arr['contactID'], $error );
 	}
 
+	public function send_customer_event( $event ): SendCustomerEventResponse {
+		$error = new WP_Error();
+
+		if ( $event instanceof Event ) {
+			$error->merge_from( $event->validate() );
+		} else {
+			$error->add( 'event', 'Event is not instance of Omnisend\SDK\V1\Event.' );
+		}
+
+		$error->merge_from( $this->check_setup() );
+
+		if ( $error->has_errors() ) {
+			return new SendCustomerEventResponse( $error );
+		}
+
+		$response = wp_remote_post(
+			OMNISEND_CORE_API_V5 . '/events',
+			array(
+				'body'    => wp_json_encode( $event->to_array() ),
+				'headers' => array(
+					'Content-Type'          => 'application/json',
+					'X-API-Key'             => $this->api_key,
+					'X-INTEGRATION-NAME'    => $this->plugin_name,
+					'X-INTEGRATION-VERSION' => $this->plugin_version,
+				),
+				'timeout' => 10,
+			)
+		);
+
+		if ( is_wp_error( $response ) ) {
+			error_log('wp_remote_post error: ' . $response->get_error_message()); // phpcs:ignore
+			return new SendCustomerEventResponse( $response );
+		}
+
+		$http_code = wp_remote_retrieve_response_code( $response );
+		if ( $http_code >= 400 ) {
+			$body    = wp_remote_retrieve_body( $response );
+			$err_msg = "HTTP error: {$http_code} - " . wp_remote_retrieve_response_message( $response ) . " - {$body}";
+			$error->add( 'omnisend_api', $err_msg );
+		}
+
+		return new SendCustomerEventResponse( $error );
+	}
+
 	/**
 	 * @return WP_Error
 	 */
diff --git a/omnisend/includes/SDK/V1/class-client.php b/omnisend/includes/SDK/V1/class-client.php
index 9a0cd88..a3c3e6a 100644
--- a/omnisend/includes/SDK/V1/class-client.php
+++ b/omnisend/includes/SDK/V1/class-client.php
@@ -7,8 +7,6 @@
 
 namespace Omnisend\SDK\V1;
 
-use WP_Error;
-
 defined( 'ABSPATH' ) || die( 'no direct access' );
 
 /**
@@ -25,4 +23,13 @@ interface Client {
 	 * @return CreateContactResponse
 	 */
 	public function create_contact( $contact ): CreateContactResponse;
+
+	/**
+	 * Send customer event to Omnisend. Customer events are used to track customer behavior and trigger automations based on that behavior.
+	 *
+	 * @param Event $event
+	 *
+	 * @return SendCustomerEventResponse
+	 */
+	public function send_customer_event( $event ): SendCustomerEventResponse;
 }
diff --git a/omnisend/includes/SDK/V1/class-contact.php b/omnisend/includes/SDK/V1/class-contact.php
index 01c037d..488f859 100644
--- a/omnisend/includes/SDK/V1/class-contact.php
+++ b/omnisend/includes/SDK/V1/class-contact.php
@@ -17,6 +17,8 @@
  *
  */
 class Contact {
+
+	private $id                 = null;
 	private $first_name         = null;
 	private $last_name          = null;
 	private $email              = null;
@@ -81,8 +83,8 @@ public function validate(): WP_Error {
 			$error->add( 'send_welcome_email', 'Not a valid boolean.' );
 		}
 
-		if ( $this->phone == null && $this->email == null ) {
-			$error->add( 'identifier', 'Phone or email must be set.' );
+		if ( $this->phone == null && $this->email == null && $this->id == null ) {
+			$error->add( 'identifier', 'Phone, email or ID must be set.' );
 		}
 
 		if ( $this->gender != null && ! in_array( $this->gender, array( 'm', 'f' ) ) ) {
@@ -175,6 +177,10 @@ public function to_array(): array {
 			$arr['identifiers'][] = $phone_identifier;
 		}
 
+		if ( $this->id ) {
+			$arr['contactID'] = $this->id;
+		}
+
 		if ( $this->first_name ) {
 			$arr['firstName'] = $this->first_name;
 		}
@@ -219,6 +225,128 @@ public function to_array(): array {
 	}
 
 
+	/**
+	 * Convert contact to array for events.
+	 *
+	 * If contact is valid it will be transformed to array that can be sent to Omnisend while creating event.
+	 *
+	 * @return array
+	 */
+	public function to_array_for_event(): array {
+		if ( $this->validate()->has_errors() ) {
+			return array();
+		}
+
+		$time_now = gmdate( 'c' );
+
+		$user_agent = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? 'user agent not found' ) );
+		$ip         = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? 'ip not found' ) );
+
+		$arr = array(
+			'consents' => array(),
+			'optIns'   => array(),
+			'tags'     => array_values( array_unique( $this->tags ) ),
+		);
+
+		if ( $this->email ) {
+			$arr['email'] = $this->email;
+
+			if ( $this->email_consent ) {
+				$email_cahnnel_consent = array(
+					'channel'   => 'email',
+					'source'    => $this->email_consent,
+					'createdAt' => $time_now,
+					'ip'        => $ip,
+					'userAgent' => $user_agent,
+				);
+
+				$arr['consents'][] = $email_cahnnel_consent;
+			}
+
+			if ( $this->email_opt_in_source ) {
+				$email_cahnnel_opt_in = array(
+					'channel'   => 'email',
+					'createdAt' => $time_now,
+					'source'    => $this->email_opt_in_source,
+				);
+
+				$arr['optIns'][] = $email_cahnnel_opt_in;
+			}
+		}
+
+		if ( $this->custom_properties ) {
+			$arr['customProperties'] = $this->custom_properties;
+		}
+
+		if ( $this->phone ) {
+			$arr['phone'] = $this->phone;
+
+			if ( $this->phone_consent ) {
+				$phone_channel_consent = array(
+					'channel'   => 'phone',
+					'source'    => $this->phone_consent,
+					'createdAt' => $time_now,
+					'ip'        => $ip,
+					'userAgent' => $user_agent,
+				);
+				$arr['consents'][]     = $phone_channel_consent;
+			}
+
+			if ( $this->phone_opt_in_source ) {
+				$phone_cahnnel_opt_in = array(
+					'channel'   => 'phone',
+					'createdAt' => $time_now,
+					'source'    => $this->phone_opt_in_source,
+				);
+
+				$arr['optIns'][] = $phone_cahnnel_opt_in;
+			}
+		}
+
+		if ( $this->id ) {
+			$arr['id'] = $this->id;
+		}
+
+		if ( $this->first_name ) {
+			$arr['firstName'] = $this->first_name;
+		}
+
+		if ( $this->last_name ) {
+			$arr['lastName'] = $this->last_name;
+		}
+
+		if ( $this->address ) {
+			$arr['address'] = $this->address;
+		}
+
+		if ( $this->city ) {
+			$arr['city'] = $this->city;
+		}
+
+		if ( $this->state ) {
+			$arr['state'] = $this->state;
+		}
+
+		if ( $this->country ) {
+			$arr['country'] = $this->country;
+		}
+
+		if ( $this->postal_code ) {
+			$arr['postalCode'] = $this->postal_code;
+		}
+
+		if ( $this->birthday ) {
+			$arr['birthdate'] = $this->birthday;
+		}
+
+		if ( $this->gender ) {
+			$arr['gender'] = $this->gender;
+		}
+
+		return $arr;
+	}
+
+
 	/**
 	 * Sets contact email.
 	 *
@@ -232,6 +360,19 @@ public function set_email( $email ): void {
 		}
 	}
 
+	/**
+	 * Sets contact id.
+	 *
+	 * @param $id
+	 *
+	 * @return void
+	 */
+	public function set_id( $id ): void {
+		if ( $id && is_string( $id ) ) {
+			$this->id = $id;
+		}
+	}
+
 	/**
 	 * Sets contact gender. It can be "m" or "f".
 	 *
@@ -400,15 +541,15 @@ public function set_email_consent( $consent_text ): void {
 		$this->email_consent = $consent_text;
 	}
 
-		/**
-		 * Sets email concent status. It's needed for GDPR compliance.
-		 *
-		 * Common format is `form:form_name` or `popup:popup_name`.
-		 *
-		 * @param $consent_text
-		 *
-		 * @return void
-		 */
+	/**
+	 * Sets email concent status. It's needed for GDPR compliance.
+	 *
+	 * Common format is `form:form_name` or `popup:popup_name`.
+	 *
+	 * @param $consent_text
+	 *
+	 * @return void
+	 */
 	public function set_phone_consent( $consent_text ): void {
 		$this->phone_consent = $consent_text;
 	}
diff --git a/omnisend/includes/SDK/V1/class-event.php b/omnisend/includes/SDK/V1/class-event.php
new file mode 100644
index 0000000..ece56bf
--- /dev/null
+++ b/omnisend/includes/SDK/V1/class-event.php
@@ -0,0 +1,162 @@
+<?php
+/**
+ * Omnisend Client
+ *
+ * @package OmnisendClient
+ */
+
+namespace Omnisend\SDK\V1;
+
+use Omnisend\SDK\V1\Contact;
+use WP_Error;
+
+defined( 'ABSPATH' ) || die( 'no direct access' );
+
+/**
+ * Omnisend Event class. It's should be used with Omnisend Client.
+ *
+ */
+class Event {
+
+	private $contact          = null;
+	private $event_name       = null;
+	private $event_version    = null;
+	private $origin           = null;
+	private array $properties = array();
+
+	/**
+	 * Validate event properties.
+	 *
+	 * It ensures that all required properties are set
+	 *
+	 * @return WP_Error
+	 */
+	public function validate(): WP_Error {
+
+		$error = new WP_Error();
+		if ( $this->contact instanceof Contact ) {
+			$error->merge_from( $this->contact->validate() );
+		}
+
+		if ( $this->event_name == null ) {
+			$error->add( 'event_name', 'Is required.' );
+		}
+
+		if ( $this->event_name != null && ! is_string( $this->event_name ) ) {
+			$error->add( 'event_name', 'Not a string.' );
+		}
+
+		if ( $this->event_version != null && ! is_string( $this->event_version ) ) {
+			$error->add( 'event_version', 'Not a string.' );
+		}
+
+		if ( $this->origin != null && ! is_string( $this->origin ) ) {
+			$error->add( 'origin', 'Not a string.' );
+		}
+
+		foreach ( $this->properties as $name ) {
+			if ( ! is_string( $name ) ) {
+				$error->add( $name, 'Not a string.' );
+			}
+		}
+
+		return $error;
+	}
+
+	/**
+	 * Sets event name.
+	 *
+	 * @param $event_name
+	 *
+	 * @return void
+	 */
+	public function set_event_name( $event_name ): void {
+		$this->event_name = $event_name;
+	}
+
+	/**
+	 * Sets event version.
+	 *
+	 * @param $event_version
+	 *
+	 * @return void
+	 */
+	public function set_event_version( $event_version ): void {
+		$this->event_version = $event_version;
+	}
+
+
+	/**
+	 * Sets event origin. Default value is api
+	 *
+	 * @param $origin
+	 *
+	 * @return void
+	 */
+	public function set_origin( $origin ): void {
+		$this->origin = $origin;
+	}
+
+	/**
+	 * @param $key
+	 * @param $value
+	 *
+	 * @return void
+	 */
+	public function add_properties( $key, $value ): void {
+		if ( $key == '' ) {
+			return;
+		}
+
+		$this->properties[ $key ] = $value;
+	}
+
+	/**
+	 * Sets contact.
+	 *
+	 * @param $contact
+	 *
+	 * @return void
+	 */
+	public function set_contact( $contact ): void {
+		$this->contact = $contact;
+	}
+
+	/**
+	 * Convert event to array.
+	 *
+	 * If event is valid it will be transformed to array that can be sent to Omnisend.
+	 *
+	 * @return array
+	 */
+	public function to_array(): array {
+		if ( $this->validate()->has_errors() ) {
+			return array();
+		}
+
+		$arr = array();
+
+		if ( $this->contact ) {
+			$arr['contact'] = $this->contact->to_array_for_event();
+		}
+
+		if ( $this->event_name ) {
+			$arr['eventName'] = $this->event_name;
+		}
+
+		$arr['origin'] = 'api';
+		if ( $this->origin ) {
+			$arr['origin'] = $this->origin;
+		}
+
+		if ( $this->event_version ) {
+			$arr['eventVersion'] = $this->event_version;
+		}
+
+		if ( $this->properties ) {
+			$arr['properties'] = $this->properties;
+		}
+
+		return $arr;
+	}
+}
diff --git a/omnisend/includes/SDK/V1/class-sendcustomereventresponse.php b/omnisend/includes/SDK/V1/class-sendcustomereventresponse.php
new file mode 100644
index 0000000..d70ff68
--- /dev/null
+++ b/omnisend/includes/SDK/V1/class-sendcustomereventresponse.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Omnisend Client
+ *
+ * @package OmnisendClient
+ */
+
+namespace Omnisend\SDK\V1;
+
+use WP_Error;
+
+defined( 'ABSPATH' ) || die( 'no direct access' );
+
+class SendCustomerEventResponse {
+
+	private WP_Error $wp_error;
+
+	/**
+	 * @param WP_Error $wp_error
+	 */
+	public function __construct( WP_Error $wp_error ) {
+		$this->wp_error = $wp_error;
+	}
+
+	public function get_wp_error(): WP_Error {
+		return $this->wp_error;
+	}
+}