diff --git a/config/initializers/postgres_partitioning.rb b/config/initializers/postgres_partitioning.rb
index f7a1ebbff86c09dc1c4e6dddaa712a50e70227eb..4157f2e79157b81fc8f0011286437d567637d7d2 100644
--- a/config/initializers/postgres_partitioning.rb
+++ b/config/initializers/postgres_partitioning.rb
@@ -106,4 +106,16 @@
   ]
 )
 
+# Enable partition management for the backfill table during web_hook_logs partitioning.
+# This way new partitions will be created as the trigger syncs new rows across to this table.
+Gitlab::Database::Partitioning.register_tables(
+  [
+    {
+      limit_connection_names: %i[main],
+      table_name: 'web_hook_logs_daily',
+      partitioned_column: :created_at, strategy: :daily, retain_for: 14.days
+    }
+  ]
+)
+
 Gitlab::Database::Partitioning.sync_partitions_ignore_db_error
diff --git a/db/docs/web_hook_logs_daily.yml b/db/docs/web_hook_logs_daily.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f34200908780b98417343adc3101bcd88351a230
--- /dev/null
+++ b/db/docs/web_hook_logs_daily.yml
@@ -0,0 +1,12 @@
+---
+table_name: web_hook_logs_daily
+classes:
+- WebHookLog
+feature_categories:
+- integrations
+description: Webhooks logs data partitioned by day.
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175379
+milestone: '17.8'
+gitlab_schema: gitlab_main_cell
+sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/463856
+table_size: small
diff --git a/db/migrate/20241217140211_create_daily_partitioned_web_hook_log_copy.rb b/db/migrate/20241217140211_create_daily_partitioned_web_hook_log_copy.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4431f0c1231912ff8e1468727318f107c6dcb028
--- /dev/null
+++ b/db/migrate/20241217140211_create_daily_partitioned_web_hook_log_copy.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class CreateDailyPartitionedWebHookLogCopy < Gitlab::Database::Migration[2.2]
+  disable_ddl_transaction!
+  milestone '17.8'
+
+  TABLE_NAME = :web_hook_logs_daily
+  SOURCE_TABLE_NAME = :web_hook_logs
+
+  def up
+    transaction do
+      execute(<<~SQL)
+        CREATE TABLE #{TABLE_NAME} (
+          LIKE #{SOURCE_TABLE_NAME} INCLUDING ALL EXCLUDING INDEXES,
+          PRIMARY KEY (id, created_at)
+        ) PARTITION BY RANGE (created_at);
+
+        CREATE TABLE IF NOT EXISTS #{partition_name(nil)}
+          PARTITION OF #{TABLE_NAME}
+          FOR VALUES FROM (MINVALUE) TO (\'#{current_date.prev_day}\');
+
+        CREATE TABLE IF NOT EXISTS #{partition_name(current_date.prev_day)}
+          PARTITION OF #{TABLE_NAME}
+          FOR VALUES FROM (\'#{current_date.prev_day}\') TO (\'#{current_date}\');
+
+        CREATE TABLE IF NOT EXISTS #{partition_name(current_date)}
+          PARTITION OF #{TABLE_NAME}
+          FOR VALUES FROM (\'#{current_date}\') TO (\'#{current_date.next_day}\');
+
+        CREATE TABLE IF NOT EXISTS #{partition_name(current_date.next_day)}
+          PARTITION OF #{TABLE_NAME}
+          FOR VALUES FROM (\'#{current_date.next_day}\') TO (\'#{current_date.next_day.next_day}\')
+      SQL
+    end
+  end
+
+  def down
+    execute(<<~SQL)
+      DROP TABLE #{TABLE_NAME}
+    SQL
+  end
+
+  private
+
+  def current_date
+    Date.current
+  end
+
+  def partition_name(date)
+    suffix = date&.strftime('%Y%m%d') || '00000000'
+    "gitlab_partitions_dynamic.#{TABLE_NAME}_#{suffix}"
+  end
+end
diff --git a/db/migrate/20241217140216_create_trigger_to_web_hook_logs.rb b/db/migrate/20241217140216_create_trigger_to_web_hook_logs.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7e2b12b80bb0e91d360cb551da57fffd8a42f858
--- /dev/null
+++ b/db/migrate/20241217140216_create_trigger_to_web_hook_logs.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class CreateTriggerToWebHookLogs < Gitlab::Database::Migration[2.2]
+  include Gitlab::Database::SchemaHelpers
+  include Gitlab::Database::PartitioningMigrationHelpers::TableManagementHelpers
+
+  milestone '17.8'
+
+  SOURCE_TABLE = :web_hook_logs
+  TARGET_TABLE = :web_hook_logs_daily
+  UNIQUE_KEY = [:id, :created_at].freeze
+
+  def up
+    create_trigger_to_sync_tables(SOURCE_TABLE, TARGET_TABLE, UNIQUE_KEY)
+  end
+
+  def down
+    drop_sync_trigger(SOURCE_TABLE)
+  end
+end
diff --git a/db/schema_migrations/20241217140211 b/db/schema_migrations/20241217140211
new file mode 100644
index 0000000000000000000000000000000000000000..2d5a79f95bf633f469d38501e3bb496516b21905
--- /dev/null
+++ b/db/schema_migrations/20241217140211
@@ -0,0 +1 @@
+db476eb6b416a71e0b45bfa186e437ffdd30e1cb71e843aedf4de7c06ab65c48
\ No newline at end of file
diff --git a/db/schema_migrations/20241217140216 b/db/schema_migrations/20241217140216
new file mode 100644
index 0000000000000000000000000000000000000000..26dab8b09b0f030495dee140ddba3abe77890af2
--- /dev/null
+++ b/db/schema_migrations/20241217140216
@@ -0,0 +1 @@
+08e0001b798c0ad73bca409cdabf8c1dcb13099b4c08e0d49fa1c10051870393
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 04ea6be00054daaa9ebd0ff93eebdfad2f3f2d26..752eed12e3de3a1f060a6fe5b0fde873a5d8966b 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -752,6 +752,64 @@ $$;
 
 COMMENT ON FUNCTION table_sync_function_0992e728d3() IS 'Partitioning migration: table sync for merge_request_diff_commits table';
 
+CREATE FUNCTION table_sync_function_29bc99d6db() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+IF (TG_OP = 'DELETE') THEN
+  DELETE FROM web_hook_logs_daily where "id" = OLD."id" AND "created_at" = OLD."created_at";
+ELSIF (TG_OP = 'UPDATE') THEN
+  UPDATE web_hook_logs_daily
+  SET "web_hook_id" = NEW."web_hook_id",
+    "trigger" = NEW."trigger",
+    "url" = NEW."url",
+    "request_headers" = NEW."request_headers",
+    "request_data" = NEW."request_data",
+    "response_headers" = NEW."response_headers",
+    "response_body" = NEW."response_body",
+    "response_status" = NEW."response_status",
+    "execution_duration" = NEW."execution_duration",
+    "internal_error_message" = NEW."internal_error_message",
+    "updated_at" = NEW."updated_at",
+    "url_hash" = NEW."url_hash"
+  WHERE web_hook_logs_daily."id" = NEW."id" AND web_hook_logs_daily."created_at" = NEW."created_at";
+ELSIF (TG_OP = 'INSERT') THEN
+  INSERT INTO web_hook_logs_daily ("id",
+    "web_hook_id",
+    "trigger",
+    "url",
+    "request_headers",
+    "request_data",
+    "response_headers",
+    "response_body",
+    "response_status",
+    "execution_duration",
+    "internal_error_message",
+    "updated_at",
+    "created_at",
+    "url_hash")
+  VALUES (NEW."id",
+    NEW."web_hook_id",
+    NEW."trigger",
+    NEW."url",
+    NEW."request_headers",
+    NEW."request_data",
+    NEW."response_headers",
+    NEW."response_body",
+    NEW."response_status",
+    NEW."execution_duration",
+    NEW."internal_error_message",
+    NEW."updated_at",
+    NEW."created_at",
+    NEW."url_hash");
+END IF;
+RETURN NULL;
+
+END
+$$;
+
+COMMENT ON FUNCTION table_sync_function_29bc99d6db() IS 'Partitioning migration: table sync for web_hook_logs table';
+
 CREATE FUNCTION table_sync_function_3f39f64fc3() RETURNS trigger
     LANGUAGE plpgsql
     AS $$
@@ -3991,6 +4049,34 @@ CREATE TABLE web_hook_logs (
 )
 PARTITION BY RANGE (created_at);
 
+CREATE SEQUENCE web_hook_logs_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+ALTER SEQUENCE web_hook_logs_id_seq OWNED BY web_hook_logs.id;
+
+CREATE TABLE web_hook_logs_daily (
+    id bigint DEFAULT nextval('web_hook_logs_id_seq'::regclass) NOT NULL,
+    web_hook_id bigint NOT NULL,
+    trigger character varying,
+    url character varying,
+    request_headers text,
+    request_data text,
+    response_headers text,
+    response_body text,
+    response_status character varying,
+    execution_duration double precision,
+    internal_error_message character varying,
+    updated_at timestamp without time zone NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    url_hash text,
+    CONSTRAINT check_df72cb58f5 CHECK ((char_length(url_hash) <= 44))
+)
+PARTITION BY RANGE (created_at);
+
 CREATE TABLE zoekt_tasks (
     id bigint NOT NULL,
     partition_id bigint DEFAULT 1 NOT NULL,
@@ -22217,15 +22303,6 @@ CREATE SEQUENCE vulnerability_user_mentions_id_seq
 
 ALTER SEQUENCE vulnerability_user_mentions_id_seq OWNED BY vulnerability_user_mentions.id;
 
-CREATE SEQUENCE web_hook_logs_id_seq
-    START WITH 1
-    INCREMENT BY 1
-    NO MINVALUE
-    NO MAXVALUE
-    CACHE 1;
-
-ALTER SEQUENCE web_hook_logs_id_seq OWNED BY web_hook_logs.id;
-
 CREATE TABLE web_hooks (
     id bigint NOT NULL,
     project_id bigint,
@@ -27436,6 +27513,9 @@ ALTER TABLE ONLY vulnerability_statistics
 ALTER TABLE ONLY vulnerability_user_mentions
     ADD CONSTRAINT vulnerability_user_mentions_pkey PRIMARY KEY (id);
 
+ALTER TABLE ONLY web_hook_logs_daily
+    ADD CONSTRAINT web_hook_logs_daily_pkey PRIMARY KEY (id, created_at);
+
 ALTER TABLE ONLY web_hook_logs
     ADD CONSTRAINT web_hook_logs_pkey PRIMARY KEY (id, created_at);
 
@@ -35743,6 +35823,8 @@ CREATE TRIGGER table_sync_trigger_57c8465cd7 AFTER INSERT OR DELETE OR UPDATE ON
 
 CREATE TRIGGER table_sync_trigger_61879721b5 AFTER INSERT OR DELETE OR UPDATE ON ci_runners FOR EACH ROW EXECUTE FUNCTION table_sync_function_686d6c7993();
 
+CREATE TRIGGER table_sync_trigger_b99eb6998c AFTER INSERT OR DELETE OR UPDATE ON web_hook_logs FOR EACH ROW EXECUTE FUNCTION table_sync_function_29bc99d6db();
+
 CREATE TRIGGER table_sync_trigger_bc3e7b56bd AFTER INSERT OR DELETE OR UPDATE ON ci_runner_machines FOR EACH ROW EXECUTE FUNCTION table_sync_function_e438f29263();
 
 CREATE TRIGGER table_sync_trigger_cd362c20e2 AFTER INSERT OR DELETE OR UPDATE ON merge_request_diff_files FOR EACH ROW EXECUTE FUNCTION table_sync_function_3f39f64fc3();
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 3456f9ce015d5c425faeb896f6198d42aa8c5e83..cef0e33f38c1f9f34af81539e8225eb31b0ea05f 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -233,6 +233,7 @@
       # See: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87584
       # Fixes performance issues with the deletion of web-hooks with many log entries
       web_hook_logs: %w[web_hook_id],
+      web_hook_logs_daily: %w[web_hook_id],
       webauthn_registrations: %w[u2f_registration_id], # this column will be dropped
       ml_candidates: %w[internal_id],
       value_stream_dashboard_counts: %w[namespace_id],
diff --git a/spec/lib/gitlab/database/sharding_key_spec.rb b/spec/lib/gitlab/database/sharding_key_spec.rb
index 6600975cab1ae4e6c3156e84ede4e9c156906029..cfd40ecd8e45a44f9c9b9b0f2709cd9b997af2c5 100644
--- a/spec/lib/gitlab/database/sharding_key_spec.rb
+++ b/spec/lib/gitlab/database/sharding_key_spec.rb
@@ -14,7 +14,8 @@
       'merge_request_diff_files_99208b8fac', # has a desired sharding key instead
       'ml_model_metadata', # has a desired sharding key instead.
       'p_ci_pipeline_variables', # has a desired sharding key instead
-      'sbom_occurrences_vulnerabilities' # has desired sharding key instead
+      'sbom_occurrences_vulnerabilities', # has desired sharding key instead
+      'web_hook_logs_daily' # temporary copy of web_hook_logs
     ]
   end