diff --git a/lib/federation/activity_stream/converter/event.ex b/lib/federation/activity_stream/converter/event.ex
index bd7dace4e..99e814eca 100644
--- a/lib/federation/activity_stream/converter/event.ex
+++ b/lib/federation/activity_stream/converter/event.ex
@@ -37,6 +37,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
 
   @online_address_name "Website"
   @banner_picture_name "Banner"
+  @ap_public "https://www.w3.org/ns/activitystreams#Public"
 
   @doc """
   Converts an AP object data to our internal data structure.
@@ -92,15 +93,15 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
   @impl Converter
   @spec model_to_as(EventModel.t()) :: map
   def model_to_as(%EventModel{} = event) do
-    to =
+    {to, cc} =
       if event.visibility == :public,
-        do: ["https://www.w3.org/ns/activitystreams#Public"],
-        else: [attributed_to_or_default(event).followers_url]
+        do: {[@ap_public], []},
+        else: {[attributed_to_or_default(event).followers_url], [@ap_public]}
 
     %{
       "type" => "Event",
       "to" => to,
-      "cc" => [],
+      "cc" => cc,
       "attributedTo" => attributed_to_or_default(event).url,
       "name" => event.title,
       "actor" =>
diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex
index c58ba3e7a..af3bb0f25 100644
--- a/lib/mobilizon/events/events.ex
+++ b/lib/mobilizon/events/events.ex
@@ -88,6 +88,8 @@ defmodule Mobilizon.Events do
     :media
   ]
 
+  @participant_preloads [:event, :actor]
+
   @doc """
   Gets a single event.
   """
@@ -307,8 +309,9 @@ defmodule Mobilizon.Events do
   """
   @spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()}
   def update_event(%Event{draft: old_draft} = old_event, attrs) do
-    with %Changeset{changes: changes} = changeset <-
-           Event.update_changeset(Repo.preload(old_event, [:tags, :media]), attrs),
+    with %Event{} = old_event <- Repo.preload(old_event, @event_preloads),
+         %Changeset{changes: changes} = changeset <-
+           Event.update_changeset(old_event, attrs),
          {:ok, %{update: %Event{} = new_event}} <-
            Multi.new()
            |> Multi.update(:update, changeset)
@@ -329,7 +332,8 @@ defmodule Mobilizon.Events do
                err -> err
              end
            end)
-           |> Repo.transaction() do
+           |> Repo.transaction(),
+         %Event{} = new_event <- Repo.preload(new_event, @event_preloads, force: true) do
       Cachex.del(:ics, "event_#{new_event.uuid}")
 
       Email.Event.calculate_event_diff_and_send_notifications(
@@ -341,7 +345,7 @@ defmodule Mobilizon.Events do
       unless new_event.draft,
         do: Workers.BuildSearch.enqueue(:update_search_event, %{"event_id" => new_event.id})
 
-      {:ok, Repo.preload(new_event, @event_preloads)}
+      {:ok, new_event}
     end
   end
 
@@ -728,7 +732,7 @@ defmodule Mobilizon.Events do
   def get_participant(participant_id) do
     Participant
     |> where([p], p.id == ^participant_id)
-    |> preload([p], [:event, :actor])
+    |> preload([p], ^@participant_preloads)
     |> Repo.one()
   end
 
@@ -744,6 +748,7 @@ defmodule Mobilizon.Events do
     case Participant
          |> where([p], event_id: ^event_id, actor_id: ^actor_id)
          |> where([p], fragment("? ->>'email' = ?", p.metadata, ^email))
+         |> preload([p], ^@participant_preloads)
          |> Repo.one() do
       %Participant{} = participant ->
         {:ok, participant}
@@ -758,6 +763,7 @@ defmodule Mobilizon.Events do
     case Participant
          |> where([p], event_id: ^event_id, actor_id: ^actor_id)
          |> where([p], fragment("? ->>'cancellation_token' = ?", p.metadata, ^cancellation_token))
+         |> preload([p], ^@participant_preloads)
          |> Repo.one() do
       %Participant{} = participant ->
         {:ok, participant}
@@ -768,7 +774,9 @@ defmodule Mobilizon.Events do
   end
 
   def get_participant(event_id, actor_id, %{}) do
-    case Repo.get_by(Participant, event_id: event_id, actor_id: actor_id) do
+    case Participant
+         |> Repo.get_by(event_id: event_id, actor_id: actor_id)
+         |> Repo.preload(@participant_preloads) do
       %Participant{} = participant ->
         {:ok, participant}
 
@@ -781,7 +789,7 @@ defmodule Mobilizon.Events do
   def get_participant_by_confirmation_token(confirmation_token) do
     Participant
     |> where([p], fragment("? ->>'confirmation_token' = ?", p.metadata, ^confirmation_token))
-    |> preload([p], [:actor, :event])
+    |> preload([p], ^@participant_preloads)
     |> Repo.one()
   end
 
diff --git a/test/federation/activity_pub/types/events_test.exs b/test/federation/activity_pub/types/events_test.exs
new file mode 100644
index 000000000..f2f934461
--- /dev/null
+++ b/test/federation/activity_pub/types/events_test.exs
@@ -0,0 +1,270 @@
+defmodule Mobilizon.Federation.ActivityPub.Types.EventsTest do
+  use Mobilizon.DataCase
+
+  import Mobilizon.Factory
+
+  alias Mobilizon.Actors.Actor
+  alias Mobilizon.Events
+  alias Mobilizon.Events.Event
+  alias Mobilizon.Federation.ActivityPub.Types.Events
+
+  @ap_public "https://www.w3.org/ns/activitystreams#Public"
+
+  describe "test event creation" do
+    @event_begins_on "2021-07-28T15:04:22Z"
+    @event_title "hey"
+    @event_data %{title: @event_title, begins_on: @event_begins_on}
+
+    test "from a simple profile" do
+      %Actor{id: organizer_actor_id, url: actor_url, followers_url: followers_url} =
+        insert(:actor)
+
+      assert {:ok, %Event{}, data} =
+               Events.create(
+                 Map.merge(@event_data, %{organizer_actor_id: organizer_actor_id}),
+                 %{}
+               )
+
+      assert match?(
+               %{
+                 "actor" => ^actor_url,
+                 "attributedTo" => ^actor_url,
+                 "cc" => [^followers_url],
+                 "object" => %{
+                   "actor" => ^actor_url,
+                   "anonymousParticipationEnabled" => false,
+                   "attachment" => [],
+                   "attributedTo" => ^actor_url,
+                   "category" => nil,
+                   "cc" => [],
+                   "commentsEnabled" => false,
+                   "content" => nil,
+                   "draft" => false,
+                   "endTime" => nil,
+                   "ical:status" => "CONFIRMED",
+                   "joinMode" => "free",
+                   "maximumAttendeeCapacity" => nil,
+                   "mediaType" => "text/html",
+                   "name" => @event_title,
+                   "repliesModerationOption" => nil,
+                   "startTime" => @event_begins_on,
+                   "tag" => [],
+                   "to" => [@ap_public],
+                   "type" => "Event"
+                 },
+                 "to" => [@ap_public],
+                 "type" => "Create"
+               },
+               data
+             )
+    end
+
+    test "an unlisted event" do
+      %Actor{id: organizer_actor_id, url: actor_url, followers_url: followers_url} =
+        insert(:actor)
+
+      assert {:ok, %Event{}, data} =
+               Events.create(
+                 Map.merge(@event_data, %{
+                   organizer_actor_id: organizer_actor_id,
+                   visibility: :unlisted
+                 }),
+                 %{}
+               )
+
+      assert match?(
+               %{
+                 "actor" => ^actor_url,
+                 "attributedTo" => ^actor_url,
+                 "cc" => [@ap_public],
+                 "object" => %{
+                   "actor" => ^actor_url,
+                   "anonymousParticipationEnabled" => false,
+                   "attachment" => [],
+                   "attributedTo" => ^actor_url,
+                   "category" => nil,
+                   "cc" => [@ap_public],
+                   "commentsEnabled" => false,
+                   "content" => nil,
+                   "draft" => false,
+                   "endTime" => nil,
+                   "ical:status" => "CONFIRMED",
+                   "joinMode" => "free",
+                   "maximumAttendeeCapacity" => nil,
+                   "mediaType" => "text/html",
+                   "name" => @event_title,
+                   "repliesModerationOption" => nil,
+                   "startTime" => @event_begins_on,
+                   "tag" => [],
+                   "to" => [^followers_url],
+                   "type" => "Event"
+                 },
+                 "to" => [^followers_url],
+                 "type" => "Create"
+               },
+               data
+             )
+    end
+
+    test "from a group member" do
+      %Actor{id: organizer_actor_id, url: actor_url} = actor = insert(:actor)
+
+      %Actor{
+        id: attributed_to_id,
+        url: group_url,
+        followers_url: followers_url,
+        members_url: members_url
+      } = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
+
+      insert(:member, parent: group, actor: actor, role: :moderator)
+
+      assert {:ok, %Event{}, data} =
+               Events.create(
+                 Map.merge(@event_data, %{
+                   organizer_actor_id: organizer_actor_id,
+                   attributed_to_id: attributed_to_id
+                 }),
+                 %{}
+               )
+
+      assert match?(
+               %{
+                 "actor" => ^actor_url,
+                 "attributedTo" => ^group_url,
+                 "cc" => [^members_url, ^followers_url],
+                 "object" => %{
+                   "actor" => ^actor_url,
+                   "anonymousParticipationEnabled" => false,
+                   "attachment" => [],
+                   "attributedTo" => ^group_url,
+                   "category" => nil,
+                   "cc" => [],
+                   "commentsEnabled" => false,
+                   "content" => nil,
+                   "draft" => false,
+                   "endTime" => nil,
+                   "ical:status" => "CONFIRMED",
+                   "joinMode" => "free",
+                   "maximumAttendeeCapacity" => nil,
+                   "mediaType" => "text/html",
+                   "name" => @event_title,
+                   "repliesModerationOption" => nil,
+                   "startTime" => @event_begins_on,
+                   "tag" => [],
+                   "to" => [@ap_public],
+                   "type" => "Event"
+                 },
+                 "to" => [@ap_public],
+                 "type" => "Create"
+               },
+               data
+             )
+    end
+  end
+
+  @event_updated_title "my event updated"
+  @event_update_data %{title: @event_updated_title}
+
+  describe "test event update" do
+    test "from a simple profile" do
+      %Actor{url: actor_url, followers_url: followers_url} = actor = insert(:actor)
+
+      {:ok, begins_on, _} = DateTime.from_iso8601(@event_begins_on)
+      %Event{} = event = insert(:event, organizer_actor: actor, begins_on: begins_on)
+
+      assert {:ok, %Event{}, data} =
+               Events.update(
+                 event,
+                 @event_update_data,
+                 %{}
+               )
+
+      assert match?(
+               %{
+                 "actor" => ^actor_url,
+                 "attributedTo" => ^actor_url,
+                 "cc" => [^followers_url],
+                 "object" => %{
+                   "actor" => ^actor_url,
+                   "anonymousParticipationEnabled" => false,
+                   "attributedTo" => ^actor_url,
+                   "cc" => [],
+                   "commentsEnabled" => false,
+                   "draft" => false,
+                   "ical:status" => "CONFIRMED",
+                   "joinMode" => "free",
+                   "maximumAttendeeCapacity" => nil,
+                   "mediaType" => "text/html",
+                   "name" => @event_updated_title,
+                   "repliesModerationOption" => nil,
+                   "startTime" => @event_begins_on,
+                   "tag" => [],
+                   "to" => [@ap_public],
+                   "type" => "Event"
+                 },
+                 "to" => [@ap_public],
+                 "type" => "Update"
+               },
+               data
+             )
+    end
+
+    test "from a group member" do
+      %Actor{} = actor_1 = insert(:actor)
+      %Actor{id: organizer_actor_2_id, url: actor_2_url} = actor_2 = insert(:actor)
+
+      %Actor{
+        url: group_url,
+        followers_url: followers_url,
+        members_url: members_url
+      } = group = insert(:group, domain: "somewhere.else", url: "https://somewhere.else/@someone")
+
+      insert(:member, parent: group, actor: actor_1, role: :moderator)
+      insert(:member, parent: group, actor: actor_2, role: :moderator)
+
+      {:ok, begins_on, _} = DateTime.from_iso8601(@event_begins_on)
+
+      %Event{} =
+        event =
+        insert(:event, organizer_actor: actor_1, begins_on: begins_on, attributed_to: group)
+
+      assert {:ok, %Event{}, data} =
+               Events.update(
+                 event,
+                 Map.merge(@event_update_data, %{
+                   organizer_actor_id: organizer_actor_2_id
+                 }),
+                 %{}
+               )
+
+      assert match?(
+               %{
+                 "actor" => ^actor_2_url,
+                 "attributedTo" => ^group_url,
+                 "cc" => [^members_url, ^followers_url],
+                 "object" => %{
+                   "actor" => ^actor_2_url,
+                   "anonymousParticipationEnabled" => false,
+                   "attributedTo" => ^group_url,
+                   "cc" => [],
+                   "commentsEnabled" => false,
+                   "draft" => false,
+                   "ical:status" => "CONFIRMED",
+                   "joinMode" => "free",
+                   "maximumAttendeeCapacity" => nil,
+                   "mediaType" => "text/html",
+                   "name" => @event_updated_title,
+                   "repliesModerationOption" => nil,
+                   "startTime" => @event_begins_on,
+                   "tag" => [],
+                   "to" => [@ap_public],
+                   "type" => "Event"
+                 },
+                 "to" => [@ap_public],
+                 "type" => "Update"
+               },
+               data
+             )
+    end
+  end
+end