grpc-ecosystem/grpc-gateway · error
%s: webhooks: %w
Error message
%s: webhooks: %w
What it means
This error is returned when the top-level `webhooks` field of an input document is not a JSON object. parse calls decodeOrderedObject on the raw value to preserve webhook insertion order; the value must be an object mapping webhook names to path items, so arrays, strings, null, or other non-object types fail and are wrapped as `<name>: webhooks: <cause>`.
Source
Thrown at openapiv3-merge/internal/merge/merge.go:221
switch key {
case "openapi":
if err := json.Unmarshal(raw, &d.OpenAPI); err != nil {
return nil, fmt.Errorf("%s: openapi: %w", in.Name, err)
}
case "info":
d.Info = raw
case "servers":
d.Servers = raw
case "paths":
obj, err := decodeOrderedObject(raw)
if err != nil {
return nil, fmt.Errorf("%s: paths: %w", in.Name, err)
}
d.Paths = obj
case "webhooks":
obj, err := decodeOrderedObject(raw)
if err != nil {
return nil, fmt.Errorf("%s: webhooks: %w", in.Name, err)
}
d.Webhooks = obj
case "components":
if !isJSONNull(raw) {
if err := json.Unmarshal(raw, d.Components); err != nil {
return nil, fmt.Errorf("%s: components: %w", in.Name, err)
}
}
case "security":
if err := json.Unmarshal(raw, &d.Security); err != nil {
return nil, fmt.Errorf("%s: security: %w", in.Name, err)
}
case "tags":
if err := json.Unmarshal(raw, &d.Tags); err != nil {
return nil, fmt.Errorf("%s: tags: %w", in.Name, err)
}
case "externalDocs":
d.ExternalDocs = rawView on GitHub (pinned to a58a4436a3)
Solutions
- Edit the input so `webhooks` is a JSON object, e.g. "webhooks": {"newPet": {...}} (use {} or omit the key if there are none).
- If serializing from code, initialize Webhooks as a map so it marshals as {} instead of null or [].
- Fix the upstream generator/tool to emit webhooks as a keyed object, matching the OpenAPI 3.1 spec shape.
- Pre-validate by decoding `webhooks` into map[string]json.RawMessage before calling Merge.
Example fix
// before
{"openapi": "3.1.0", "webhooks": ["newPet"]}
// after
{"openapi": "3.1.0", "webhooks": {"newPet": {"post": {"responses": {"200": {"description": "ok"}}}}}} Defensive patterns
Strategy: validation
Validate before calling
func validateWebhooksField(data []byte) error {
var doc struct {
Webhooks json.RawMessage `json:"webhooks"`
}
if err := json.Unmarshal(data, &doc); err != nil {
return err
}
if len(doc.Webhooks) == 0 || string(doc.Webhooks) == "null" {
return nil
}
var m map[string]json.RawMessage
if err := json.Unmarshal(doc.Webhooks, &m); err != nil {
return fmt.Errorf("webhooks must be a JSON object: %w", err)
}
return nil
} Type guard
func isWebhooksObject(raw json.RawMessage) bool {
var m map[string]json.RawMessage
return len(raw) > 0 && json.Unmarshal(raw, &m) == nil
} Try / catch
merged, err := merger.Merge(inputs)
if err != nil {
if strings.Contains(err.Error(), ": webhooks: ") {
return fmt.Errorf("input spec has a non-object webhooks field: %w", err)
}
return err
} Prevention
- Emit webhooks as a keyed object per OpenAPI 3.1 (webhooks: {name: pathItem}), never an array.
- Initialize webhook containers as maps when generating specs so they marshal to {} not null.
- Validate specs against the OpenAPI 3.1 schema before merging.
- If the source tool emits webhooks as a list, transform it to a keyed object in your ingestion pipeline.
When it happens
Trigger: Calling Merge with an Input whose Data contains `"webhooks": []` (array), `"webhooks": "..."`, `"webhooks": null`, or any other non-object JSON value under `webhooks`.
Common situations: Specs generated from structs where Webhooks was a nil slice serialized as null/[]; templates that scaffold webhooks as a list; tools that emit webhooks as an array of objects; hand-edited specs where the map braces were lost.
Related errors
AI-assisted analysis of grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02).
Data as JSON: /api/errors/bb1a247537686d86.
Report an issue: GitHub.