diff --git a/scripts/qapi/golang.py b/scripts/qapi/golang.py
index bc6206797a..81b320d6dd 100644
--- a/scripts/qapi/golang.py
+++ b/scripts/qapi/golang.py
@@ -215,6 +215,54 @@
 }}
 """
 
+TEMPLATE_EVENT = """
+type Timestamp struct {{
+\tSeconds      int64 `json:"seconds"`
+\tMicroseconds int64 `json:"microseconds"`
+}}
+
+type Event interface {{
+\tGetName() string
+\tGetTimestamp() Timestamp
+}}
+
+func MarshalEvent(e Event) ([]byte, error) {{
+\tm := make(map[string]any)
+\tm["event"] = e.GetName()
+\tm["timestamp"] = e.GetTimestamp()
+\tif bytes, err := json.Marshal(e); err != nil {{
+\t\treturn []byte{{}}, err
+\t}} else if len(bytes) > 2 {{
+\t\tm["data"] = e
+\t}}
+\treturn json.Marshal(m)
+}}
+
+func UnmarshalEvent(data []byte) (Event, error) {{
+\tbase := struct {{
+\t\tName             string    `json:"event"`
+\t\tMessageTimestamp Timestamp `json:"timestamp"`
+\t}}{{}}
+\tif err := json.Unmarshal(data, &base); err != nil {{
+\t\treturn nil, fmt.Errorf("Failed to decode event: %s", string(data))
+\t}}
+
+\tswitch base.Name {{{cases}
+\t}}
+\treturn nil, errors.New("Failed to recognize event")
+}}
+"""
+
+TEMPLATE_EVENT_METHODS = """
+func (s *{type_name}) GetName() string {{
+\treturn "{name}"
+}}
+
+func (s *{type_name}) GetTimestamp() Timestamp {{
+\treturn s.MessageTimestamp
+}}
+"""
+
 
 def gen_golang(schema: QAPISchema, output_dir: str, prefix: str) -> None:
     vis = QAPISchemaGenGolangVisitor(prefix)
@@ -238,7 +286,7 @@ def qapi_to_field_name_enum(name: str) -> str:
     return name.title().replace("-", "")
 
 
-def qapi_to_go_type_name(name: str) -> str:
+def qapi_to_go_type_name(name: str, meta: Optional[str] = None) -> str:
     if qapi_name_is_object(name):
         name = name[6:]
 
@@ -253,6 +301,11 @@ def qapi_to_go_type_name(name: str) -> str:
 
     name += "".join(word.title() for word in words[1:])
 
+    types = ["event"]
+    if meta in types:
+        name = name[:-3] if name.endswith("Arg") else name
+        name += meta.title().replace(" ", "")
+
     return name
 
 
@@ -608,6 +661,15 @@ def qapi_to_golang_struct(
 
     discriminator = None if not variants else variants.tag_member.name
     fields, with_nullable = recursive_base(self, base, discriminator)
+    if info.defn_meta == "event":
+        fields.insert(
+            0,
+            {
+                "name": "MessageTimestamp",
+                "type": "Timestamp",
+                "tag": """`json:"-"`""",
+            },
+        )
 
     if members:
         for member in members:
@@ -651,7 +713,7 @@ def qapi_to_golang_struct(
                 fields.append(field)
                 with_nullable = True if nullable else with_nullable
 
-    type_name = qapi_to_go_type_name(name)
+    type_name = qapi_to_go_type_name(name, info.defn_meta)
     content = generate_struct_type(type_name, fields, ident)
     if with_nullable:
         content += struct_with_nullable_generate_marshal(
@@ -825,6 +887,28 @@ def generate_template_alternate(
     return content
 
 
+def generate_template_event(events: dict[str, Tuple[str, str]]) -> str:
+    cases = ""
+    content = ""
+    for name in sorted(events):
+        case_type, gocode = events[name]
+        content += gocode
+        cases += f"""
+\tcase "{name}":
+\t\tevent := struct {{
+\t\t\tData {case_type} `json:"data"`
+\t\t}}{{}}
+
+\t\tif err := json.Unmarshal(data, &event); err != nil {{
+\t\t\treturn nil, fmt.Errorf("Failed to unmarshal: %s", string(data))
+\t\t}}
+\t\tevent.Data.MessageTimestamp = base.MessageTimestamp
+\t\treturn &event.Data, nil
+"""
+    content += TEMPLATE_EVENT.format(cases=cases)
+    return content
+
+
 def generate_content_from_dict(data: dict[str, str]) -> str:
     content = ""
 
@@ -841,12 +925,14 @@ def __init__(self, _: str):
         types = (
             "alternate",
             "enum",
+            "event",
             "helper",
             "struct",
             "union",
         )
         self.target = dict.fromkeys(types, "")
         self.schema: QAPISchema
+        self.events: dict[str, Tuple[str, str]] = {}
         self.golang_package_name = "qapi"
         self.enums: dict[str, str] = {}
         self.alternates: dict[str, str] = {}
@@ -901,6 +987,7 @@ def visit_end(self) -> None:
         self.target["alternate"] += generate_content_from_dict(self.alternates)
         self.target["struct"] += generate_content_from_dict(self.structs)
         self.target["union"] += generate_content_from_dict(self.unions)
+        self.target["event"] += generate_template_event(self.events)
 
     def visit_object_type(
         self,
@@ -1027,7 +1114,36 @@ def visit_event(
         arg_type: Optional[QAPISchemaObjectType],
         boxed: bool,
     ) -> None:
-        pass
+        assert name == info.defn_name
+        assert name not in self.events
+        type_name = qapi_to_go_type_name(name, info.defn_meta)
+
+        if isinstance(arg_type, QAPISchemaObjectType):
+            content = qapi_to_golang_struct(
+                self,
+                name,
+                arg_type.info,
+                arg_type.ifcond,
+                arg_type.features,
+                arg_type.base,
+                arg_type.members,
+                arg_type.variants,
+            )
+        else:
+            args: List[dict[str:str]] = []
+            args.append(
+                {
+                    "name": "MessageTimestamp",
+                    "type": "Timestamp",
+                    "tag": """`json:"-"`""",
+                }
+            )
+            content = generate_struct_type(type_name, args)
+
+        content += TEMPLATE_EVENT_METHODS.format(
+            name=name, type_name=type_name
+        )
+        self.events[name] = (type_name, content)
 
     def write(self, output_dir: str) -> None:
         for module_name, content in self.target.items():
