diff --git a/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch
new file mode 100644
index 0000000000..c505c77d38
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/item/component/ResolvableProfile.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/item/component/ResolvableProfile.java
++++ b/net/minecraft/world/item/component/ResolvableProfile.java
+@@ -49,7 +49,7 @@
+         if (this.isResolved()) {
+             return CompletableFuture.completedFuture(this);
+         } else {
+-            return this.id.isPresent() ? SkullBlockEntity.fetchGameProfile(this.id.get()).thenApply(optional -> {
++            return this.id.isPresent() ? SkullBlockEntity.fetchGameProfile(this.id.get(), this.name.orElse(null)).thenApply(optional -> { // Paper - player profile events
+                 GameProfile gameProfile = optional.orElseGet(() -> new GameProfile(this.id.get(), this.name.orElse("")));
+                 return new ResolvableProfile(gameProfile);
+             }) : SkullBlockEntity.fetchGameProfile(this.name.orElseThrow()).thenApply(profile -> {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
index 348568625e..a683a1cb1b 100644
--- a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SkullBlockEntity.java.patch
@@ -1,7 +1,54 @@
 --- a/net/minecraft/world/level/block/entity/SkullBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-@@ -105,7 +105,7 @@
-                 ProfileResult profileResult = apiServices.sessionService().fetchProfile(uuid, true);
+@@ -41,7 +41,7 @@
+     @Nullable
+     private static LoadingCache<String, CompletableFuture<Optional<GameProfile>>> profileCacheByName;
+     @Nullable
+-    private static LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> profileCacheById;
++    private static LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID,  @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> profileCacheById; // Paper - player profile events
+     public static final Executor CHECKED_MAIN_THREAD_EXECUTOR = runnable -> {
+         Executor executor = mainThreadExecutor;
+         if (executor != null) {
+@@ -76,9 +76,9 @@
+         profileCacheById = CacheBuilder.newBuilder()
+             .expireAfterAccess(Duration.ofMinutes(10L))
+             .maximumSize(256L)
+-            .build(new CacheLoader<UUID, CompletableFuture<Optional<GameProfile>>>() {
++            .build(new CacheLoader<>() { // Paper - player profile events
+                 @Override
+-                public CompletableFuture<Optional<GameProfile>> load(UUID uUID) {
++                public CompletableFuture<Optional<GameProfile>> load(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> uUID) { // Paper - player profile events
+                     return SkullBlockEntity.fetchProfileById(uUID, apiServices, booleanSupplier);
+                 }
+             });
+@@ -89,23 +89,29 @@
+             .getAsync(name)
+             .thenCompose(
+                 optional -> {
+-                    LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
++                    LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById; // Paper - player profile events
+                     return loadingCache != null && !optional.isEmpty()
+-                        ? loadingCache.getUnchecked(optional.get().getId()).thenApply(optional2 -> optional2.or(() -> optional))
++                        ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(optional.get().getId(), optional.get())).thenApply(optional2 -> optional2.or(() -> optional)) // Paper - player profile events
+                         : CompletableFuture.completedFuture(Optional.empty());
+                 }
+             );
+     }
+ 
+-    static CompletableFuture<Optional<GameProfile>> fetchProfileById(UUID uuid, Services apiServices, BooleanSupplier booleanSupplier) {
++    static CompletableFuture<Optional<GameProfile>> fetchProfileById(com.mojang.datafixers.util.Pair<java.util.UUID, @org.jetbrains.annotations.Nullable GameProfile> pair, Services apiServices, BooleanSupplier booleanSupplier) { // Paper
+         return CompletableFuture.supplyAsync(() -> {
+             if (booleanSupplier.getAsBoolean()) {
+                 return Optional.empty();
+             } else {
+-                ProfileResult profileResult = apiServices.sessionService().fetchProfile(uuid, true);
++                // Paper start - fill player profile events
++                if (apiServices.sessionService() instanceof com.destroystokyo.paper.profile.PaperMinecraftSessionService paperService) {
++                    final GameProfile profile = pair.getSecond() != null ? pair.getSecond() : new com.mojang.authlib.GameProfile(pair.getFirst(), "");
++                    return Optional.ofNullable(paperService.fetchProfile(profile, true)).map(ProfileResult::profile);
++                }
++                ProfileResult profileResult = apiServices.sessionService().fetchProfile(pair.getFirst(), true);
++                // Paper end - fill player profile events
                  return Optional.ofNullable(profileResult).map(ProfileResult::profile);
              }
 -        }, Util.backgroundExecutor().forName("fetchProfile"));
@@ -9,3 +56,18 @@
      }
  
      public static void clear() {
+@@ -210,9 +216,11 @@
+             : CompletableFuture.completedFuture(Optional.empty());
+     }
+ 
+-    public static CompletableFuture<Optional<GameProfile>> fetchGameProfile(UUID uuid) {
+-        LoadingCache<UUID, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
+-        return loadingCache != null ? loadingCache.getUnchecked(uuid) : CompletableFuture.completedFuture(Optional.empty());
++    // Paper start - player profile events
++    public static CompletableFuture<Optional<GameProfile>> fetchGameProfile(UUID uuid, @Nullable String name) {
++        LoadingCache<com.mojang.datafixers.util.Pair<java.util.UUID,  @org.jetbrains.annotations.Nullable GameProfile>, CompletableFuture<Optional<GameProfile>>> loadingCache = profileCacheById;
++        return loadingCache != null ? loadingCache.getUnchecked(new com.mojang.datafixers.util.Pair<>(uuid, name != null ? new com.mojang.authlib.GameProfile(uuid, name) : null)) : CompletableFuture.completedFuture(Optional.empty());
++        // Paper end - player profile events
+     }
+ 
+     @Override
diff --git a/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java
index 985e6fc43a..d577384797 100644
--- a/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java
+++ b/paper-server/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java
@@ -1,6 +1,7 @@
 package com.destroystokyo.paper.profile;
 
 import com.mojang.authlib.Environment;
+import com.mojang.authlib.GameProfile;
 import com.mojang.authlib.yggdrasil.ProfileResult;
 import com.mojang.authlib.yggdrasil.ServicesKeySet;
 import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService;
@@ -15,7 +16,21 @@ public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionServi
         super(servicesKeySet, proxy, environment);
     }
 
-    @Override
+    public @Nullable ProfileResult fetchProfile(GameProfile profile, final boolean requireSecure) {
+        CraftPlayerProfile playerProfile = (CraftPlayerProfile) CraftPlayerProfile.asBukkitMirror(profile);
+        new com.destroystokyo.paper.event.profile.PreFillProfileEvent(playerProfile).callEvent();
+        profile = playerProfile.getGameProfile();
+        if (profile.getProperties().containsKey("textures")) {
+            return new ProfileResult(profile, java.util.Collections.emptySet());
+        }
+        ProfileResult result = super.fetchProfile(profile.getId(), requireSecure);
+        if (result != null) {
+            new com.destroystokyo.paper.event.profile.FillProfileEvent(CraftPlayerProfile.asBukkitMirror(result.profile())).callEvent();
+        }
+        return result;
+    }
+
+    @Override @io.papermc.paper.annotation.DoNotUse @Deprecated
     public @Nullable ProfileResult fetchProfile(final UUID profileId, final boolean requireSecure) {
         return super.fetchProfile(profileId, requireSecure);
     }