diff --git a/hook.wsgi b/hook.wsgi
index 320af2d18f0ca6e88f88b2202e8d39abd8653b1f..61a11404af8187c54356850a70c5ee8fb1920dcc 100644
--- a/hook.wsgi
+++ b/hook.wsgi
@@ -7,7 +7,6 @@ import psycopg2
 import psycopg2.extras
 import traceback
 import dateutil.parser
-# import dateutil.tz
 
 ### Configuration ###
 
@@ -43,11 +42,8 @@ def db_query(query, args=()):
 
 # FIXME: Move to shared code?
 
-def parse_time(iso_time, tz_name):
-    utc = dateutil.parser.isoparse(iso_time)
-    # tz = dateutil.tz.gettz(tz_name)
-    # return utc.astimezone(tz)
-    return utc
+def parse_time(iso_time):
+    return dateutil.parser.isoparse(iso_time)
 
 ### Application ###
 
@@ -80,12 +76,10 @@ class HookApp:
                 uid,
                 meeting['topic'],
                 meeting['type'],
-                parse_time(meeting['start_time'], meeting['timezone']),
+                parse_time(meeting['start_time']),
                 meeting['duration'],
             ))
 
-        db_connection.commit()
-
 
     def create_recurring_meeting(self, uid, meeting):
         meeting_id = meeting["id"]
@@ -105,12 +99,10 @@ class HookApp:
                     uid,
                     meeting['topic'],
                     meeting['type'],
-                    parse_time(occ['start_time'], meeting['timezone']),
+                    parse_time(occ['start_time']),
                     occ['duration'],
                 ))
 
-        db_connection.commit()
-
 
     def create_meeting(self, js):
         payload = js["payload"]
@@ -145,8 +137,6 @@ class HookApp:
                 meeting_id,
             ))
 
-        db_connection.commit()
-
 
     def delete_recurring_meeting(self, meeting):
         meeting_id = meeting["id"]
@@ -165,8 +155,6 @@ class HookApp:
                     occ_id,
                 ))
 
-        db_connection.commit()
-
 
     def delete_meeting(self, js):
         payload = js["payload"]
@@ -183,6 +171,138 @@ class HookApp:
             return
 
 
+    def update_common_attrs(self, meeting_id, occurrence_id, new):
+        if "topic" in new:
+            self.log(f"Meeting {meeting_id}.{occurrence_id}: Updating topic")
+            db_query("UPDATE zoom_meetings SET topic=%s WHERE meeting_id=%s AND (occurrence_id=%s OR %s = '0')",
+                     (new["topic"], meeting_id, occurrence_id, occurrence_id))
+
+        for a in ['uuid', 'host_id']:
+            if a in new:
+                self.log(f"Meeting {meeting_id}.{occurrence_id}: Change of {a} not supported")
+
+
+    def update_schedule(self, meeting_id, occurrence_id, new):
+        if "start_time" in new:
+            self.log(f"Meeting {meeting_id}.{occurrence_id}: Updating start time")
+            db_query("UPDATE zoom_meetings SET start_time=%s WHERE meeting_id=%s AND (occurrence_id=%s OR %s = '0')",
+                     (parse_time(new['start_time']), meeting_id, occurrence_id, occurrence_id))
+
+        if "duration" in new:
+            self.log(f"Meeting {meeting_id}.{occurrence_id}: Updating duration")
+            db_query("UPDATE zoom_meetings SET duration=%s WHERE meeting_id=%s AND (occurrence_id=%s OR %s = '0')",
+                     (new['duration'], meeting_id, occurrence_id, occurrence_id))
+
+
+    def update_regular_meeting(self, meeting_id, old, new):
+        self.log(f"Meeting {meeting_id}: Updating regular meeting")
+        self.update_common_attrs(meeting_id, 0, new)
+        self.update_schedule(meeting_id, 0, new)
+
+
+    def update_recurring_meeting_single(self, meeting_id, old, new):
+        self.log(f"Meeting {meeting_id}: Updating single occurrence")
+        for occ in new["occurrences"]:
+            self.update_common_attrs(meeting_id, occ["occurrence_id"], new)
+            self.update_schedule(meeting_id, occ["occurrence_id"], new)     # e.g., duration can be set here
+            self.update_schedule(meeting_id, occ["occurrence_id"], occ)
+
+
+    def update_recurring_meeting_all(self, meeting_id, old, new):
+        meeting_id = new["id"]
+        self.log(f"Meeting {meeting_id}: Updating all occurrences")
+
+        if "occurrences" not in new:
+            self.update_common_attrs(meeting_id, 0, new)
+            return
+
+        db_query("SELECT * FROM zoom_meetings WHERE meeting_id=%s", (meeting_id,))
+        orig = db.fetchone()
+        if orig is None:
+            self.log(f"Meeting {meeting_id}: Update did not find previous version")
+            return
+
+        db_query("DELETE FROM zoom_meetings WHERE meeting_id=%s", (meeting_id,))
+
+        for occ in new["occurrences"]:
+            occurrence_id = occ["occurrence_id"]
+            self.log(f"Meeting {meeting_id}.{occurrence_id}: Re-creating")
+
+            db_query("""
+                    INSERT INTO zoom_meetings
+                    (meeting_id, uuid, occurrence_id, host_id, topic, type, start_time, duration)
+                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
+                """, (
+                    meeting_id,
+                    new.get('uuid', orig.uuid),
+                    occurrence_id,
+                    orig.host_id,
+                    new.get('topic', orig.topic),
+                    new.get('type', orig.type),
+                    parse_time(occ['start_time']),
+                    occ['duration'],
+                ))
+
+
+    def update_regular_to_recurring(self, meeting_id, old, new):
+        self.log(f"Meeting {meeting_id}: Type change to recurring")
+        self.update_recurring_meeting_all(meeting_id, old, new)
+
+
+    def update_recurring_to_regular(self, meeting_id, old, new):
+        self.log(f"Meeting {meeting_id}: Type change to regular")
+
+        db_query("SELECT * FROM zoom_meetings WHERE meeting_id=%s", (meeting_id,))
+        orig = db.fetchone()
+        if orig is None:
+            self.log(f"Meeting {meeting_id}: Update did not find previous version")
+            return
+
+        db_query("DELETE FROM zoom_meetings WHERE meeting_id=%s", (meeting_id,))
+
+        db_query("""
+                INSERT INTO zoom_meetings
+                (meeting_id, uuid, host_id, topic, type, start_time, duration)
+                VALUES (%s, %s, %s, %s, %s, %s, %s)
+            """, (
+                meeting_id,
+                new.get('uuid', orig.uuid),
+                orig.host_id,
+                new.get('topic', orig.topic),
+                new['type'],
+                parse_time(new['start_time']),
+                new['duration'],
+            ))
+
+
+    def update_meeting(self, js):
+        payload = js["payload"]
+        new = payload["object"]
+        old = payload["old_object"]
+        meeting_id = new["id"]
+
+        new_type = new.get("type", -1)
+        old_type = old.get("type", -1)
+        if old_type != new_type:
+            if old_type == 2 and new_type == 8:
+                self.update_regular_to_recurring(meeting_id, old, new)
+            elif old_type == 8 and new_type == 2:
+                self.update_recurring_to_regular(meeting_id, old, new)
+            else:
+                self.log(f"Meeting {meeting_id}: Unsupported type change from {old_type} to {new_type}")
+            return
+
+        scope = payload.get("scope", "")
+        if scope == "":
+            self.update_regular_meeting(meeting_id, old, new)
+        elif scope == "all":
+            self.update_recurring_meeting_all(meeting_id, old, new)
+        elif scope == "single":
+            self.update_recurring_meeting_single(meeting_id, old, new)
+        else:
+            self.log(f"Meeting {meeting_id}: Unsupported update scope {scope}")
+
+
     def run(self):
         method = self.env['REQUEST_METHOD']
         if method != 'POST':
@@ -201,12 +321,17 @@ class HookApp:
             self.create_meeting(js)
         elif event == "meeting.deleted":
             self.delete_meeting(js)
+        elif event == "meeting.updated":
+            self.update_meeting(js)
         else:
             self.log(f"Unknown event: {event}")
 
+        db_connection.commit()
+
         self.wsgi_start("204 No Content", [])
         return b""
 
+
 def application(env, start_response):
     app = HookApp(env, start_response)
     try: