hyperledger/fabric · error

channel header in envelope must contain timestamp

Error message

channel header in envelope must contain timestamp

What it means

validateChannelHeader requires the channel header timestamp field to be present. The timestamp is used to reject replayed/stale deliver requests; if chdr.Timestamp is nil the request cannot be time-validated and is rejected.

Source

Thrown at common/deliver/deliver.go:393

		return nil, nil, nil, err
	}

	shdr, err := protoutil.UnmarshalSignatureHeader(payload.Header.SignatureHeader)
	if err != nil {
		return nil, nil, nil, err
	}

	err = h.validateChannelHeader(ctx, chdr)
	if err != nil {
		return nil, nil, nil, err
	}

	return payload, chdr, shdr, nil
}

func (h *Handler) validateChannelHeader(ctx context.Context, chdr *cb.ChannelHeader) error {
	if chdr.GetTimestamp() == nil {
		err := errors.New("channel header in envelope must contain timestamp")
		return err
	}

	envTime := time.Unix(chdr.GetTimestamp().Seconds, int64(chdr.GetTimestamp().Nanos)).UTC()
	serverTime := time.Now()

	if math.Abs(float64(serverTime.UnixNano()-envTime.UnixNano())) > float64(h.TimeWindow.Nanoseconds()) {
		err := errors.Errorf("envelope timestamp %s is more than %s apart from current server time %s", envTime, h.TimeWindow, serverTime)
		return err
	}

	err := h.BindingInspector.Inspect(ctx, chdr)
	if err != nil {
		return err
	}

	return nil
}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Set Timestamp: protoutil.CurrentTimestampBytes or googleprotobuf Timestamp via proto now() when constructing the ChannelHeader.
  2. Regenerate the envelope with an SDK/helper (e.g. protoutil.CreateEnvelope) that fills the timestamp automatically.
  3. If encountering a third-party client, upgrade it to a version that sets the timestamp.

Example fix

// before
chdr := &cb.ChannelHeader{Type: int32(cb.HeaderType_DELIVER_SEEK_INFO), ChannelId: chID}

// after
chdr := &cb.ChannelHeader{Type: int32(cb.HeaderType_DELIVER_SEEK_INFO), ChannelId: chID, Timestamp: protoutil.CurrentTimestampBytes()}
Defensive patterns

Strategy: validation

Validate before calling

chdr, err := protoutil.UnmarshalChannelHeader(payload.Header.ChannelHeader)
if err != nil || chdr.GetTimestamp() == nil {
    return errors.New("channel header must carry a timestamp before sending to deliver")
}

Type guard

func hasTimestamp(chdr *cb.ChannelHeader) bool { return chdr != nil && chdr.GetTimestamp() != nil }

Try / catch

if err := h.validateChannelHeader(ctx, chdr); err != nil {
    if strings.Contains(err.Error(), "must contain timestamp") {
        // regenerate envelope with protoutil.CurrentTimestampBytes()
    }
    return err
}

Prevention

When it happens

Trigger: An envelope passes payload/header unmarshalling (parseEnvelope) but the ChannelHeader was built without setting Timestamp (e.g. manual construction of cb.ChannelHeader with only Type and ChannelId) before being sent to the deliver service.

Common situations: Hand-rolled deliver clients that build ChannelHeader structs directly; SDK or tool versions that stopped auto-filling timestamps; test fixtures with minimal headers.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/1df55a307c9838d5. Report an issue: GitHub.