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 = raw

View on GitHub (pinned to a58a4436a3)

Solutions

  1. Edit the input so `webhooks` is a JSON object, e.g. "webhooks": {"newPet": {...}} (use {} or omit the key if there are none).
  2. If serializing from code, initialize Webhooks as a map so it marshals as {} instead of null or [].
  3. Fix the upstream generator/tool to emit webhooks as a keyed object, matching the OpenAPI 3.1 spec shape.
  4. 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

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.