diff --git a/lib/wp/endpoint.c b/lib/wp/endpoint.c
index 2ce211a4dce7c85eee7497cd343e0684530ca850..a2f86350a739d1cbc30495b68b8cd996fc79025d 100644
--- a/lib/wp/endpoint.c
+++ b/lib/wp/endpoint.c
@@ -737,39 +737,199 @@ wp_endpoint_get_links (WpEndpoint * self)
 typedef struct _WpEndpointLinkPrivate WpEndpointLinkPrivate;
 struct _WpEndpointLinkPrivate
 {
-  WpEndpoint *src;
+  /* The task to signal the endpoint link is initialized */
+  GTask *init_task;
+
+  GWeakRef src;
   guint32 src_stream;
-  WpEndpoint *sink;
+  GWeakRef sink;
   guint32 sink_stream;
 };
 
-G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (WpEndpointLink, wp_endpoint_link, G_TYPE_OBJECT)
+enum {
+  LINKPROP_0,
+  LINKPROP_SRC,
+  LINKPROP_SRC_STREAM,
+  LINKPROP_SINK,
+  LINKPROP_SINK_STREAM,
+};
+
+static void wp_endpoint_link_async_initable_init (gpointer iface,
+    gpointer iface_data);
+
+G_DEFINE_ABSTRACT_TYPE_WITH_CODE (WpEndpointLink, wp_endpoint_link, G_TYPE_OBJECT,
+    G_ADD_PRIVATE (WpEndpointLink)
+    G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE,
+                           wp_endpoint_link_async_initable_init))
 
 static void
-wp_endpoint_link_init (WpEndpointLink * self)
+endpoint_link_finalize (GObject * object)
 {
+  WpEndpointLinkPrivate *priv =
+      wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (object));
+
+  /* Destroy the init task */
+  g_clear_object(&priv->init_task);
+
+  /* Clear the endpoint weak reaferences */
+  g_weak_ref_clear(&priv->src);
+  g_weak_ref_clear(&priv->sink);
 }
 
 static void
-wp_endpoint_link_class_init (WpEndpointLinkClass * klass)
+endpoint_link_set_property (GObject * object, guint property_id,
+    const GValue * value, GParamSpec * pspec)
 {
+  WpEndpointLinkPrivate *priv =
+      wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (object));
+
+  switch (property_id) {
+  case LINKPROP_SRC:
+    g_weak_ref_set (&priv->src, g_value_get_object (value));
+    break;
+  case LINKPROP_SRC_STREAM:
+    priv->src_stream = g_value_get_uint(value);
+    break;
+  case LINKPROP_SINK:
+    g_weak_ref_set (&priv->sink, g_value_get_object (value));
+    break;
+  case LINKPROP_SINK_STREAM:
+    priv->sink_stream = g_value_get_uint(value);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
 }
 
-void
-wp_endpoint_link_set_endpoints (WpEndpointLink * self, WpEndpoint * src,
-    guint32 src_stream, WpEndpoint * sink, guint32 sink_stream)
+static void
+endpoint_link_get_property (GObject * object, guint property_id,
+    GValue * value, GParamSpec * pspec)
 {
-  WpEndpointLinkPrivate *priv;
+  WpEndpointLinkPrivate *priv =
+      wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (object));
 
-  g_return_if_fail (WP_IS_ENDPOINT_LINK (self));
-  g_return_if_fail (WP_IS_ENDPOINT (src));
-  g_return_if_fail (WP_IS_ENDPOINT (sink));
+  switch (property_id) {
+  case LINKPROP_SRC:
+    g_value_take_object (value, g_weak_ref_get (&priv->src));
+    break;
+  case LINKPROP_SRC_STREAM:
+    g_value_set_uint (value, priv->src_stream);
+    break;
+  case LINKPROP_SINK:
+    g_value_take_object (value, g_weak_ref_get (&priv->sink));
+    break;
+  case LINKPROP_SINK_STREAM:
+    g_value_set_uint (value, priv->sink_stream);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
 
-  priv = wp_endpoint_link_get_instance_private (self);
-  priv->src = src;
-  priv->src_stream = src_stream;
-  priv->sink = sink;
-  priv->sink_stream = sink_stream;
+static void
+wp_endpoint_link_init_async (GAsyncInitable *initable, int io_priority,
+    GCancellable *cancellable, GAsyncReadyCallback callback, gpointer data)
+{
+  WpEndpointLink *link = WP_ENDPOINT_LINK(initable);
+  WpEndpointLinkPrivate *priv =
+      wp_endpoint_link_get_instance_private (WP_ENDPOINT_LINK (initable));
+  g_autoptr (WpEndpoint) src = g_weak_ref_get (&priv->src);
+  g_autoptr (WpEndpoint) sink = g_weak_ref_get (&priv->sink);
+  g_autoptr (GError) error = NULL;
+  g_autoptr (GVariant) src_props = NULL;
+  g_autoptr (GVariant) sink_props = NULL;
+  WpEndpointPrivate *endpoint_priv;
+
+  /* Create the async task */
+  priv->init_task = g_task_new (initable, cancellable, callback, data);
+
+  /* Prepare the endpoints */
+  if (!WP_ENDPOINT_GET_CLASS (src)->prepare_link (src, priv->src_stream, link,
+      &src_props, &error)) {
+    g_task_return_error (priv->init_task, error);
+    g_clear_object(&priv->init_task);
+    return;
+  }
+  if (!WP_ENDPOINT_GET_CLASS (sink)->prepare_link (sink, priv->sink_stream,
+      link, &sink_props, &error)) {
+    g_task_return_error (priv->init_task, error);
+    g_clear_object(&priv->init_task);
+    return;
+  }
+
+  /* Create the link */
+  g_return_if_fail (WP_ENDPOINT_LINK_GET_CLASS (link)->create);
+  if (!WP_ENDPOINT_LINK_GET_CLASS (link)->create (link, src_props,
+      sink_props, &error)) {
+    g_task_return_error (priv->init_task, error);
+    g_clear_object(&priv->init_task);
+    return;
+  }
+
+  /* Register the link on the endpoints */
+  endpoint_priv = wp_endpoint_get_instance_private (src);
+  g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
+  endpoint_priv = wp_endpoint_get_instance_private (sink);
+  g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
+
+  /* Finish the creation of the endpoint */
+  g_task_return_boolean (priv->init_task, TRUE);
+  g_clear_object(&priv->init_task);
+}
+
+static gboolean
+wp_endpoint_link_init_finish (GAsyncInitable *initable, GAsyncResult *result,
+    GError **error)
+{
+  g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+wp_endpoint_link_async_initable_init (gpointer iface, gpointer iface_data)
+{
+  GAsyncInitableIface *ai_iface = iface;
+
+  ai_iface->init_async = wp_endpoint_link_init_async;
+  ai_iface->init_finish = wp_endpoint_link_init_finish;
+}
+
+static void
+wp_endpoint_link_init (WpEndpointLink * self)
+{
+  WpEndpointLinkPrivate *priv = wp_endpoint_link_get_instance_private (self);
+
+  /* Init the endpoint weak references */
+  g_weak_ref_init (&priv->src, NULL);
+  g_weak_ref_init (&priv->sink, NULL);
+}
+
+static void
+wp_endpoint_link_class_init (WpEndpointLinkClass * klass)
+{
+  GObjectClass *object_class = (GObjectClass *) klass;
+
+  object_class->finalize = endpoint_link_finalize;
+  object_class->set_property = endpoint_link_set_property;
+  object_class->get_property = endpoint_link_get_property;
+
+  g_object_class_install_property (object_class, LINKPROP_SRC,
+      g_param_spec_object ("src", "src", "The src endpoint", WP_TYPE_ENDPOINT,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, LINKPROP_SRC_STREAM,
+      g_param_spec_uint ("src-stream", "src-stream", "The src stream",
+          0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, LINKPROP_SINK,
+      g_param_spec_object ("sink", "sink", "The sink endpoint", WP_TYPE_ENDPOINT,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, LINKPROP_SINK_STREAM,
+      g_param_spec_uint ("sink-stream", "sink-stream", "The sink stream",
+          0, G_MAXUINT, 0,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 }
 
 WpEndpoint *
@@ -780,7 +940,7 @@ wp_endpoint_link_get_source_endpoint (WpEndpointLink * self)
   g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), NULL);
 
   priv = wp_endpoint_link_get_instance_private (self);
-  return priv->src;
+  return g_weak_ref_get (&priv->src);
 }
 
 guint32
@@ -802,7 +962,7 @@ wp_endpoint_link_get_sink_endpoint (WpEndpointLink * self)
   g_return_val_if_fail (WP_IS_ENDPOINT_LINK (self), NULL);
 
   priv = wp_endpoint_link_get_instance_private (self);
-  return priv->sink;
+  return g_weak_ref_get (&priv->sink);
 }
 
 guint32
@@ -816,19 +976,19 @@ wp_endpoint_link_get_sink_stream (WpEndpointLink * self)
   return priv->sink_stream;
 }
 
-WpEndpointLink * wp_endpoint_link_new (WpCore * core, WpEndpoint * src,
-    guint32 src_stream, WpEndpoint * sink, guint32 sink_stream, GError ** error)
+void
+wp_endpoint_link_new (WpCore * core, WpEndpoint * src, guint32 src_stream,
+    WpEndpoint * sink, guint32 sink_stream, GAsyncReadyCallback ready,
+    gpointer data)
 {
-  g_autoptr (WpEndpointLink) link = NULL;
-  g_autoptr (GVariant) src_props = NULL;
-  g_autoptr (GVariant) sink_props = NULL;
   const gchar *src_factory = NULL, *sink_factory = NULL;
-  WpEndpointPrivate *endpoint_priv;
+  GVariantBuilder b;
+  g_autoptr (GVariant) link_props = NULL;
 
-  g_return_val_if_fail (WP_IS_ENDPOINT (src), NULL);
-  g_return_val_if_fail (WP_IS_ENDPOINT (sink), NULL);
-  g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (src)->prepare_link, NULL);
-  g_return_val_if_fail (WP_ENDPOINT_GET_CLASS (sink)->prepare_link, NULL);
+  g_return_if_fail (WP_IS_ENDPOINT (src));
+  g_return_if_fail (WP_IS_ENDPOINT (sink));
+  g_return_if_fail (WP_ENDPOINT_GET_CLASS (src)->prepare_link);
+  g_return_if_fail (WP_ENDPOINT_GET_CLASS (sink)->prepare_link);
 
   /* find the factory */
 
@@ -839,52 +999,38 @@ WpEndpointLink * wp_endpoint_link_new (WpCore * core, WpEndpoint * src,
 
   if (src_factory || sink_factory) {
     if (src_factory && sink_factory && strcmp (src_factory, sink_factory) != 0) {
-      g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVALID_ARGUMENT,
-          "It is not possible to link endpoints that both specify different "
-          "custom link factories");
-      return NULL;
+      g_critical ("It is not possible to link endpoints that both specify"
+          "different custom link factories");
+      return;
     } else if (sink_factory)
       src_factory = sink_factory;
   } else {
     src_factory = "pipewire-simple-endpoint-link";
   }
 
-  /* create link object */
-
-  link = wp_factory_make (core, src_factory, WP_TYPE_ENDPOINT_LINK, NULL);
-  if (!link) {
-    g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_OPERATION_FAILED,
-        "Failed to create link object from factory %s", src_factory);
-    return NULL;
-  }
-  g_return_val_if_fail (WP_ENDPOINT_LINK_GET_CLASS (link)->create, NULL);
-
-  /* prepare the link */
-
-  wp_endpoint_link_set_endpoints (link, src, src_stream, sink, sink_stream);
-
-  if (!WP_ENDPOINT_GET_CLASS (src)->prepare_link (src, src_stream, link,
-          &src_props, error))
-    return NULL;
-  if (!WP_ENDPOINT_GET_CLASS (sink)->prepare_link (sink, sink_stream, link,
-          &sink_props, error))
-    return NULL;
-
-  /* create the link */
-
-  if (!WP_ENDPOINT_LINK_GET_CLASS (link)->create (link, src_props, sink_props,
-          error))
-    return NULL;
-
-  /* register the link on the endpoints */
-
-  endpoint_priv = wp_endpoint_get_instance_private (src);
-  g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
-
-  endpoint_priv = wp_endpoint_get_instance_private (sink);
-  g_ptr_array_add (endpoint_priv->links, g_object_ref (link));
+  /* Build the properties */
+  g_variant_builder_init (&b, G_VARIANT_TYPE_VARDICT);
+  g_variant_builder_add (&b, "{sv}", "src",
+      g_variant_new_uint64 ((guint64)src));
+  g_variant_builder_add (&b, "{sv}", "src-stream",
+      g_variant_new_uint32 (src_stream));
+  g_variant_builder_add (&b, "{sv}", "sink",
+      g_variant_new_uint64 ((guint64)sink));
+  g_variant_builder_add (&b, "{sv}", "sink-stream",
+      g_variant_new_uint32 (sink_stream));
+  link_props = g_variant_builder_end (&b);
+
+  /* Create the link object async */
+  wp_factory_make (core, src_factory, WP_TYPE_ENDPOINT_LINK, link_props, ready,
+      data);
+}
 
-  return link;
+WpEndpointLink *
+wp_endpoint_link_new_finish (GObject *initable, GAsyncResult *res,
+    GError **error)
+{
+  GAsyncInitable *ai = G_ASYNC_INITABLE(initable);
+  return WP_ENDPOINT_LINK(g_async_initable_new_finish(ai, res, error));
 }
 
 void
@@ -892,21 +1038,29 @@ wp_endpoint_link_destroy (WpEndpointLink * self)
 {
   WpEndpointLinkPrivate *priv;
   WpEndpointPrivate *endpoint_priv;
+  g_autoptr (WpEndpoint) src = NULL;
+  g_autoptr (WpEndpoint) sink = NULL;
 
   g_return_if_fail (WP_IS_ENDPOINT_LINK (self));
   g_return_if_fail (WP_ENDPOINT_LINK_GET_CLASS (self)->destroy);
 
   priv = wp_endpoint_link_get_instance_private (self);
+  src = g_weak_ref_get (&priv->src);
+  sink = g_weak_ref_get (&priv->sink);
 
-  WP_ENDPOINT_LINK_GET_CLASS (self)->destroy (self);
-  if (WP_ENDPOINT_GET_CLASS (priv->src)->release_link)
-    WP_ENDPOINT_GET_CLASS (priv->src)->release_link (priv->src, self);
-  if (WP_ENDPOINT_GET_CLASS (priv->sink)->release_link)
-    WP_ENDPOINT_GET_CLASS (priv->sink)->release_link (priv->sink, self);
+  if (src && WP_ENDPOINT_GET_CLASS (src)->release_link)
+    WP_ENDPOINT_GET_CLASS (src)->release_link (src, self);
+  if (sink && WP_ENDPOINT_GET_CLASS (sink)->release_link)
+    WP_ENDPOINT_GET_CLASS (sink)->release_link (sink, self);
 
-  endpoint_priv = wp_endpoint_get_instance_private (priv->src);
-  g_ptr_array_remove_fast (endpoint_priv->links, self);
+  if (src) {
+    endpoint_priv = wp_endpoint_get_instance_private (src);
+    g_ptr_array_remove_fast (endpoint_priv->links, self);
+  }
+  if (sink) {
+    endpoint_priv = wp_endpoint_get_instance_private (sink);
+    g_ptr_array_remove_fast (endpoint_priv->links, self);
+  }
 
-  endpoint_priv = wp_endpoint_get_instance_private (priv->sink);
-  g_ptr_array_remove_fast (endpoint_priv->links, self);
+  WP_ENDPOINT_LINK_GET_CLASS (self)->destroy (self);
 }
diff --git a/lib/wp/endpoint.h b/lib/wp/endpoint.h
index 4c5c4b5ad9cd717b82dfa7add8b8babb3bd4f69c..a6cdfa8185d6b7d36d337d6b2cb37907f9395f58 100644
--- a/lib/wp/endpoint.h
+++ b/lib/wp/endpoint.h
@@ -78,17 +78,16 @@ struct _WpEndpointLinkClass
   void (*destroy) (WpEndpointLink * self);
 };
 
-void wp_endpoint_link_set_endpoints (WpEndpointLink * self, WpEndpoint * src,
-    guint32 src_stream, WpEndpoint * sink, guint32 sink_stream);
-
 WpEndpoint * wp_endpoint_link_get_source_endpoint (WpEndpointLink * self);
 guint32 wp_endpoint_link_get_source_stream (WpEndpointLink * self);
 WpEndpoint * wp_endpoint_link_get_sink_endpoint (WpEndpointLink * self);
 guint32 wp_endpoint_link_get_sink_stream (WpEndpointLink * self);
 
-WpEndpointLink * wp_endpoint_link_new (WpCore * core, WpEndpoint * src,
+void wp_endpoint_link_new (WpCore * core, WpEndpoint * src,
     guint32 src_stream, WpEndpoint * sink, guint32 sink_stream,
-    GError ** error);
+    GAsyncReadyCallback ready, gpointer data);
+WpEndpointLink * wp_endpoint_link_new_finish (GObject *initable,
+    GAsyncResult *res, GError **error);
 void wp_endpoint_link_destroy (WpEndpointLink * self);
 
 G_END_DECLS
diff --git a/lib/wp/factory.c b/lib/wp/factory.c
index 82cb6562e7b8f657bef8d25cc657365d84a98b0f..3093c337acb85a3a7bde2ec87d8220f1639d3002 100644
--- a/lib/wp/factory.c
+++ b/lib/wp/factory.c
@@ -15,10 +15,7 @@ struct _WpFactory
   GWeakRef core;
   gchar *name;
   GQuark name_quark;
-  union {
-    WpFactoryFunc sync;
-    WpFactoryAsyncFunc async;
-  } create_object;
+  WpFactoryFunc create_object;
 };
 
 G_DEFINE_TYPE (WpFactory, wp_factory, G_TYPE_OBJECT)
@@ -48,29 +45,20 @@ wp_factory_class_init (WpFactoryClass * klass)
   object_class->finalize = wp_factory_finalize;
 }
 
-static
-WpFactory * create_factory (WpCore * core, const gchar * name)
+WpFactory *
+wp_factory_new (WpCore * core, const gchar * name,
+    WpFactoryFunc func)
 {
   WpFactory *f = NULL;
 
+  g_return_val_if_fail (func, NULL);
   g_return_val_if_fail (name != NULL && *name != '\0', NULL);
 
   f = g_object_new (WP_TYPE_FACTORY, NULL);
   g_weak_ref_init (&f->core, core);
   f->name = g_strdup (name);
   f->name_quark = g_quark_from_string (f->name);
-
-  return f;
-}
-
-WpFactory *
-wp_factory_new (WpCore * core, const gchar * name, WpFactoryFunc func)
-{
-  WpFactory *f = NULL;
-  g_return_val_if_fail (func, NULL);
-
-  f = create_factory(core, name);
-  f->create_object.sync = func;
+  f->create_object = func;
 
   g_info ("WpFactory:%p new factory: %s", f, name);
 
@@ -79,23 +67,6 @@ wp_factory_new (WpCore * core, const gchar * name, WpFactoryFunc func)
   return f;
 }
 
-WpFactory *
-wp_factory_new_async (WpCore * core, const gchar * name,
-    WpFactoryAsyncFunc func)
-{
-  WpFactory *f = NULL;
-  g_return_val_if_fail (func, NULL);
-
-  f = create_factory(core, name);
-  f->create_object.async = func;
-
-  g_info ("WpFactory:%p new async factory: %s", f, name);
-
-  wp_core_register_global (core, WP_GLOBAL_FACTORY, f, g_object_unref);
-
-  return f;
-}
-
 const gchar *
 wp_factory_get_name (WpFactory * self)
 {
@@ -114,23 +85,14 @@ wp_factory_get_core (WpFactory * self)
   return g_weak_ref_get (&self->core);
 }
 
-gpointer
-wp_factory_create_object (WpFactory * self, GType type, GVariant * properties)
-{
-  g_debug ("WpFactory:%p (%s) create object of type %s", self, self->name,
-      g_type_name (type));
-
-  return self->create_object.sync (self, type, properties);
-}
-
 void
-wp_factory_create_object_async (WpFactory * self, GType type,
+wp_factory_create_object (WpFactory * self, GType type,
     GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
 {
-  g_debug ("WpFactory:%p (%s) create object async of type %s", self, self->name,
+  g_debug ("WpFactory:%p (%s) create object of type %s", self, self->name,
       g_type_name (type));
 
-  self->create_object.async (self, type, properties, ready, user_data);
+  self->create_object (self, type, properties, ready, user_data);
 }
 
 struct find_factory_data
@@ -160,20 +122,11 @@ wp_factory_find (WpCore * core, const gchar * name)
   return d.ret;
 }
 
-gpointer
-wp_factory_make (WpCore * core, const gchar * name, GType type,
-    GVariant * properties)
-{
-  WpFactory *f = wp_factory_find (core, name);
-  if (!f) return NULL;
-  return wp_factory_create_object (f, type, properties);
-}
-
 void
-wp_factory_make_async (WpCore * core, const gchar * name, GType type,
+wp_factory_make (WpCore * core, const gchar * name, GType type,
     GVariant * properties, GAsyncReadyCallback ready, gpointer user_data)
 {
   WpFactory *f = wp_factory_find (core, name);
   if (!f) return;
-  wp_factory_create_object_async (f, type, properties, ready, user_data);
+  wp_factory_create_object (f, type, properties, ready, user_data);
 }
diff --git a/lib/wp/factory.h b/lib/wp/factory.h
index 9f74c22c593b3a5e4db3694d5275c2e7cb2f47c4..c43739d334e9b2792be6b54e4f2baa67b1b5dd2e 100644
--- a/lib/wp/factory.h
+++ b/lib/wp/factory.h
@@ -18,27 +18,19 @@ G_BEGIN_DECLS
 #define WP_TYPE_FACTORY (wp_factory_get_type ())
 G_DECLARE_FINAL_TYPE (WpFactory, wp_factory, WP, FACTORY, GObject)
 
-typedef gpointer (*WpFactoryFunc) (WpFactory * self, GType type,
-    GVariant * properties);
-typedef void (*WpFactoryAsyncFunc) (WpFactory * self, GType type,
+typedef void (*WpFactoryFunc) (WpFactory * self, GType type,
     GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
 
 WpFactory * wp_factory_new (WpCore * core, const gchar * name,
     WpFactoryFunc func);
-WpFactory * wp_factory_new_async (WpCore * core, const gchar * name,
-    WpFactoryAsyncFunc func);
 
 const gchar * wp_factory_get_name (WpFactory * self);
 WpCore * wp_factory_get_core (WpFactory * self);
-gpointer wp_factory_create_object (WpFactory * self, GType type,
-    GVariant * properties);
-void wp_factory_create_object_async (WpFactory * self, GType type,
+void wp_factory_create_object (WpFactory * self, GType type,
     GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
 
 WpFactory * wp_factory_find (WpCore * core, const gchar * name);
-gpointer wp_factory_make (WpCore * core, const gchar * name, GType type,
-    GVariant * properties);
-void wp_factory_make_async (WpCore * core, const gchar * name, GType type,
+void wp_factory_make (WpCore * core, const gchar * name, GType type,
     GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
 
 G_END_DECLS
diff --git a/modules/module-pipewire.c b/modules/module-pipewire.c
index 9394b1e6e30b27fbb4287b252ae21857ca4e867a..5df312395704471b13ca9b7ec94338febd237d81 100644
--- a/modules/module-pipewire.c
+++ b/modules/module-pipewire.c
@@ -19,8 +19,8 @@ void remote_endpoint_init (WpCore * core, struct pw_core * pw_core,
     struct pw_remote * remote);
 void simple_endpoint_factory (WpFactory * factory, GType type,
     GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
-gpointer simple_endpoint_link_factory (WpFactory * factory, GType type,
-    GVariant * properties);
+void simple_endpoint_link_factory (WpFactory * factory, GType type,
+    GVariant * properties, GAsyncReadyCallback ready, gpointer user_data);
 
 struct module_data
 {
@@ -89,7 +89,7 @@ on_node_added (WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
   endpoint_props = g_variant_builder_end (&b);
 
   /* Create the endpoint async */
-  wp_factory_make_async (core, "pipewire-simple-endpoint", WP_TYPE_ENDPOINT,
+  wp_factory_make (core, "pipewire-simple-endpoint", WP_TYPE_ENDPOINT,
       endpoint_props, on_endpoint_created, data);
 }
 
@@ -162,7 +162,7 @@ wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
   remote_endpoint_init (core, pw_core, pw_remote);
 
   /* Register simple-endpoint and simple-endpoint-link */
-  wp_factory_new_async (core, "pipewire-simple-endpoint",
+  wp_factory_new (core, "pipewire-simple-endpoint",
       simple_endpoint_factory);
   wp_factory_new (core, "pipewire-simple-endpoint-link",
       simple_endpoint_link_factory);
diff --git a/modules/module-pipewire/simple-endpoint-link.c b/modules/module-pipewire/simple-endpoint-link.c
index a7e8edc71090c2cfa32fadd1d7a9c6c9356459b0..a0dc2b8db175d048e25cbe4eb237e122d7290fd9 100644
--- a/modules/module-pipewire/simple-endpoint-link.c
+++ b/modules/module-pipewire/simple-endpoint-link.c
@@ -27,8 +27,13 @@ struct _WpPipewireSimpleEndpointLink
 {
   WpEndpointLink parent;
 
-  /* The core proxy */
-  struct pw_core_proxy *core_proxy;
+  /* The wireplumber core */
+  GWeakRef core;
+};
+
+enum {
+  PROP_0,
+  PROP_CORE,
 };
 
 G_DECLARE_FINAL_TYPE (WpPipewireSimpleEndpointLink,
@@ -40,6 +45,51 @@ G_DEFINE_TYPE (WpPipewireSimpleEndpointLink,
 static void
 simple_endpoint_link_init (WpPipewireSimpleEndpointLink * self)
 {
+  /* Init the core weak reference */
+  g_weak_ref_init (&self->core, NULL);
+}
+
+static void
+simple_endpoint_link_finalize (GObject * object)
+{
+  WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(object);
+
+  /* Clear the core weak reference */
+  g_weak_ref_clear (&self->core);
+}
+
+static void
+simple_endpoint_link_set_property (GObject * object, guint property_id,
+    const GValue * value, GParamSpec * pspec)
+{
+  WpPipewireSimpleEndpointLink *self =
+      WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK (object);
+
+  switch (property_id) {
+  case PROP_CORE:
+    g_weak_ref_set (&self->core, g_value_get_object (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+static void
+simple_endpoint_link_get_property (GObject * object, guint property_id,
+    GValue * value, GParamSpec * pspec)
+{
+  WpPipewireSimpleEndpointLink *self =
+      WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK (object);
+
+  switch (property_id) {
+  case PROP_CORE:
+    g_value_take_object (value, g_weak_ref_get (&self->core));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
 }
 
 static gboolean
@@ -47,12 +97,18 @@ simple_endpoint_link_create (WpEndpointLink * epl, GVariant * src_data,
     GVariant * sink_data, GError ** error)
 {
   WpPipewireSimpleEndpointLink *self = WP_PIPEWIRE_SIMPLE_ENDPOINT_LINK(epl);
+  g_autoptr (WpCore) core = g_weak_ref_get (&self->core);
+  WpRemotePipewire *remote_pipewire;
   struct pw_properties *props;
   guint32 output_node_id, input_node_id;
   GVariant *src_ports, *sink_ports;
   GVariantIter *out_iter, *in_iter;
   guint64 out_ptr, in_ptr;
 
+  /* Get the remote pipewire */
+  remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE);
+  g_return_val_if_fail (remote_pipewire, FALSE);
+
   /* Get the node ids and port ids */
   if (!g_variant_lookup (src_data, "node-id", "u", &output_node_id))
       return FALSE;
@@ -90,8 +146,8 @@ simple_endpoint_link_create (WpEndpointLink * epl, GVariant * src_data,
       pw_properties_setf(props, PW_LINK_INPUT_PORT_ID, "%d", in_id);
 
       /* Create the link */
-      pw_core_proxy_create_object(self->core_proxy, "link-factory",
-          PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0);
+      wp_remote_pipewire_create_object(remote_pipewire, "link-factory",
+          PW_TYPE_INTERFACE_Link, &props->dict);
 
       /* Clean up */
       pw_properties_free(props);
@@ -112,49 +168,54 @@ simple_endpoint_link_destroy (WpEndpointLink * self)
 static void
 simple_endpoint_link_class_init (WpPipewireSimpleEndpointLinkClass * klass)
 {
+  GObjectClass *object_class = (GObjectClass *) klass;
   WpEndpointLinkClass *link_class = (WpEndpointLinkClass *) klass;
 
+  object_class->finalize = simple_endpoint_link_finalize;
+  object_class->set_property = simple_endpoint_link_set_property;
+  object_class->get_property = simple_endpoint_link_get_property;
+
   link_class->create = simple_endpoint_link_create;
   link_class->destroy = simple_endpoint_link_destroy;
+
+  g_object_class_install_property (object_class, PROP_CORE,
+      g_param_spec_object ("core", "core",
+          "The wireplumber core object this links belongs to", WP_TYPE_CORE,
+          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 }
 
-gpointer
+void
 simple_endpoint_link_factory (WpFactory * factory, GType type,
-    GVariant * properties)
+    GVariant * properties, GAsyncReadyCallback ready, gpointer data)
 {
-  WpCore *wp_core = NULL;
-  WpRemote *remote;
-  struct pw_remote *pw_remote;
+  g_autoptr(WpCore) core = NULL;
+  guint64 src, sink;
+  guint src_stream, sink_stream;
 
   /* Make sure the type is an endpoint link */
-  if (type != WP_TYPE_ENDPOINT_LINK)
-    return NULL;
-
-  /* Get the WirePlumber core */
-  wp_core = wp_factory_get_core(factory);
-  if (!wp_core) {
-    g_warning("failed to get wireplumbe core. Skipping...");
-    return NULL;
-  }
-
-  /* Get the remote */
-  remote = wp_core_get_global(wp_core, WP_GLOBAL_REMOTE_PIPEWIRE);
-  if (!remote) {
-    g_warning("failed to get core remote. Skipping...");
-    return NULL;
-  }
+  g_return_if_fail (type == WP_TYPE_ENDPOINT_LINK);
+
+  /* Get the Core */
+  core = wp_factory_get_core (factory);
+  g_return_if_fail (core);
+
+  /* Get the properties */
+  if (!g_variant_lookup (properties, "src", "t", &src))
+      return;
+  if (!g_variant_lookup (properties, "src-stream", "u", &src_stream))
+      return;
+  if (!g_variant_lookup (properties, "sink", "t", &sink))
+      return;
+  if (!g_variant_lookup (properties, "sink-stream", "u", &sink_stream))
+      return;
 
   /* Create the endpoint link */
-  WpPipewireSimpleEndpointLink *epl = g_object_new (
-      simple_endpoint_link_get_type (), NULL);
-
-  /* Set the core proxy */
-  g_object_get (remote, "pw-remote", &pw_remote, NULL);
-  epl->core_proxy = pw_remote_get_core_proxy(pw_remote);
-  if (!epl->core_proxy) {
-    g_warning("failed to get core proxy. Skipping...");
-    return NULL;
-  }
-
-  return epl;
+  g_async_initable_new_async (
+      simple_endpoint_link_get_type (), G_PRIORITY_DEFAULT, NULL, ready, data,
+      "src", (gpointer)src,
+      "src-stream", src_stream,
+      "sink", (gpointer)sink,
+      "sink-stream", sink_stream,
+      "core", core,
+      NULL);
 }
diff --git a/modules/module-pipewire/simple-endpoint.c b/modules/module-pipewire/simple-endpoint.c
index 3ca0fac1e12cf8a175b904fa817d075e9e466174..5067ed5edc9bed1f4d7e8faa13b3bee85912f2a7 100644
--- a/modules/module-pipewire/simple-endpoint.c
+++ b/modules/module-pipewire/simple-endpoint.c
@@ -36,6 +36,9 @@ struct _WpPipewireSimpleEndpoint
   /* Handler */
   gulong proxy_node_done_handler_id;
 
+  /* Direction */
+  enum pw_direction direction;
+
   /* Proxies */
   WpProxyNode *proxy_node;
   struct spa_hook node_proxy_listener;
@@ -199,7 +202,7 @@ emit_endpoint_ports(WpPipewireSimpleEndpoint *self)
   param = spa_format_audio_raw_build(&pod_builder, SPA_PARAM_Format, &format);
   param = spa_pod_builder_add_object(&pod_builder,
       SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile,
-      SPA_PARAM_PROFILE_direction,  SPA_POD_Id(PW_DIRECTION_OUTPUT),
+      SPA_PARAM_PROFILE_direction,  SPA_POD_Id(self->direction),
       SPA_PARAM_PROFILE_format,     SPA_POD_Pod(param));
 
   /* Set the param profile to emit the ports */
@@ -261,6 +264,7 @@ wp_simple_endpoint_init_async (GAsyncInitable *initable, int io_priority,
 {
   WpPipewireSimpleEndpoint *self = WP_PIPEWIRE_SIMPLE_ENDPOINT (initable);
   g_autoptr (WpCore) core = wp_endpoint_get_core(WP_ENDPOINT(self));
+  const gchar *media_class = wp_endpoint_get_media_class (WP_ENDPOINT (self));
   struct pw_node_proxy *node_proxy = NULL;
 
   /* Create the async task */
@@ -269,6 +273,14 @@ wp_simple_endpoint_init_async (GAsyncInitable *initable, int io_priority,
   /* Init the proxies_port array */
   self->proxies_port = g_ptr_array_new_full(2, (GDestroyNotify)g_object_unref);
 
+  /* Set the direction */
+  if (g_str_has_prefix (media_class, "Stream/Input"))
+    self->direction = PW_DIRECTION_INPUT;
+  else if (g_str_has_prefix (media_class, "Stream/Output"))
+    self->direction = PW_DIRECTION_OUTPUT;
+  else
+    g_critical ("failed to parse direction");
+
   /* Register a port_added callback */
   self->remote_pipewire = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE);
   g_return_if_fail(self->remote_pipewire);
diff --git a/modules/module-pw-alsa-udev.c b/modules/module-pw-alsa-udev.c
index 74c34979e6e326224e771735aab63926fec4d60d..a353cad4636d2ce6d5e971748d93be3a11426647 100644
--- a/modules/module-pw-alsa-udev.c
+++ b/modules/module-pw-alsa-udev.c
@@ -76,7 +76,7 @@ on_node_added(WpRemotePipewire *rp, guint id, guint parent_id, gconstpointer p,
   endpoint_props = g_variant_builder_end (&b);
 
   /* Create the endpoint async */
-  wp_factory_make_async (core, "pw-audio-softdsp-endpoint", WP_TYPE_ENDPOINT,
+  wp_factory_make (core, "pw-audio-softdsp-endpoint", WP_TYPE_ENDPOINT,
       endpoint_props, on_endpoint_created, impl);
 }
 
diff --git a/modules/module-pw-audio-softdsp-endpoint.c b/modules/module-pw-audio-softdsp-endpoint.c
index dd2d5bc7a6c0f2150b9d5d1c047a5f5c96894e1b..49a5c6c0d066cbacb927262103136c133926d8f8 100644
--- a/modules/module-pw-audio-softdsp-endpoint.c
+++ b/modules/module-pw-audio-softdsp-endpoint.c
@@ -727,5 +727,5 @@ void
 wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
 {
   /* Register the softdsp endpoint */
-  wp_factory_new_async (core, "pw-audio-softdsp-endpoint", endpoint_factory);
+  wp_factory_new (core, "pw-audio-softdsp-endpoint", endpoint_factory);
 }
diff --git a/modules/module-simple-policy.c b/modules/module-simple-policy.c
index d47818eab699f0fd2f5e7ce2ef7bcc396041fb25..312f604fd7df852c6c6c0be00283b3b7473cd31d 100644
--- a/modules/module-simple-policy.c
+++ b/modules/module-simple-policy.c
@@ -225,6 +225,29 @@ simple_policy_endpoint_removed (WpPolicy *policy, WpEndpoint *ep)
       g_object_ref (self), g_object_unref);
 }
 
+static void
+on_endpoint_link_created(GObject *initable, GAsyncResult *res, gpointer d)
+{
+  g_autoptr (WpEndpointLink) link = NULL;
+  g_autoptr (GError) error = NULL;
+  g_autoptr (WpEndpoint) src_ep = NULL;
+  g_autoptr (WpEndpoint) sink_ep = NULL;
+
+  /* Get the link */
+  link = wp_endpoint_link_new_finish(initable, res, &error);
+  g_return_if_fail (link);
+
+  /* Log linking info */
+  if (error) {
+    g_warning ("Could not link endpoints: %s\n", error->message);
+  } else {
+    src_ep = wp_endpoint_link_get_source_endpoint (link);
+    sink_ep = wp_endpoint_link_get_sink_endpoint (link);
+    g_info ("Sucessfully linked '%s' to '%s'\n", wp_endpoint_get_name (src_ep),
+        wp_endpoint_get_name (sink_ep));
+  }
+}
+
 static gboolean
 simple_policy_handle_endpoint (WpPolicy *policy, WpEndpoint *ep)
 {
@@ -232,34 +255,40 @@ simple_policy_handle_endpoint (WpPolicy *policy, WpEndpoint *ep)
   GVariantDict d;
   g_autoptr (WpCore) core = NULL;
   g_autoptr (WpEndpoint) target = NULL;
-  g_autoptr (GError) error = NULL;
   guint32 stream_id;
+  gboolean is_sink = FALSE;
 
-  /* TODO: For now we only accept audio output clients */
+  /* TODO: For now we only accept audio stream clients */
   media_class = wp_endpoint_get_media_class(ep);
-  if (!g_str_equal (media_class, "Stream/Output/Audio"))
+  if (!g_str_has_prefix (media_class, "Stream") ||
+      !g_str_has_suffix (media_class, "Audio"))
     return FALSE;
 
+  /* Detect if the client is a sink or a source */
+  is_sink = g_str_has_prefix (media_class, "Stream/Input");
+
   /* Locate the target endpoint */
   g_variant_dict_init (&d, NULL);
   g_variant_dict_insert (&d, "action", "s", "link");
-  g_variant_dict_insert (&d, "media.class", "s", "Audio/Sink");
+  g_variant_dict_insert (&d, "media.class", "s",
+      is_sink ? "Audio/Source" : "Audio/Sink");
   /* TODO: more properties are needed here */
 
   core = wp_policy_get_core (policy);
   target = wp_policy_find_endpoint (core, g_variant_dict_end (&d), &stream_id);
   if (!target) {
-    g_warning ("Could not find an Audio/Sink target endpoint\n");
+    g_warning ("Could not find a target endpoint\n");
     /* TODO: we should kill the client, otherwise it's going to hang waiting */
     return FALSE;
   }
 
   /* Link the client with the target */
-  if (!wp_endpoint_link_new (core, ep, 0, target, stream_id, &error)) {
-    g_warning ("Could not link endpoints: %s\n", error->message);
+  if (is_sink) {
+    wp_endpoint_link_new (core, target, 0, ep, stream_id,
+        on_endpoint_link_created, NULL);
   } else {
-    g_info ("Sucessfully linked '%s' to '%s'\n", wp_endpoint_get_name (ep),
-        wp_endpoint_get_name (target));
+    wp_endpoint_link_new (core, ep, 0, target, stream_id,
+        on_endpoint_link_created, NULL);
   }
 
   return TRUE;