Skip to content

gRPC Metadata — Sending Custom Headers for Auth, Tracing, and Routing

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about grpc metadata. We cover key concepts, practical examples, and best practices to help you master this topic.

gRPC metadata provides key-value pairs sent alongside RPC calls as headers or trailers, enabling authentication tokens, correlation IDs, tracing context, and custom routing information to flow between clients and servers.

What You'll Learn

  • What gRPC metadata is and how it differs from HTTP headers
  • Sending metadata from clients
  • Reading metadata on servers
  • Setting response headers and trailers
  • Metadata conventions for auth, tracing, and routing

Why It Matters

Metadata carries cross-cutting concerns like auth tokens, tracing spans, and correlation IDs without polluting the protobuf message definitions. This keeps your service contracts clean while supporting infrastructure requirements. DodaTech's Durga Antivirus Pro passes JWT tokens, correlation IDs, and region hints through gRPC metadata across 50+ Microservices.

Real-World Use

A request arrives at the API Gateway with a JWT token in the Authorization header. The gateway extracts the token and passes it as gRPC metadata. The downstream service reads the metadata, validates the token, and uses the user ID from it — all without adding auth fields to the protobuf definition.

sequenceDiagram
    participant Client
    participant Server
    Client->>Server: Unary Call
    Note over Client,Server: Metadata (headers):
authorization: bearer xyz
correlation-id: abc-123
x-region: us-east Server->>Server: Extract metadata Server->>Server: Validate token Server->>Server: Process request Server-->>Client: Response Note over Server,Client: Trailers:
x-request-id: req-456
x-process-time: 42ms

Code Examples

Example 1: Sending Metadata from Go Client

package main

import (
    "context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
)

func sendRequest() {
    conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
    defer conn.Close()
    
    client := pb.NewThreatServiceClient(conn)
    
    // Create metadata
    md := metadata.Pairs(
        "authorization", "Bearer eyJhbGci...",
        "correlation-id", "abc-123-def-456",
        "x-region", "us-east-1",
    )
    
    // Send with outgoing context
    ctx := metadata.NewOutgoingContext(context.Background(), md)
    
    response, err := client.ReportThreat(ctx, &pb.ThreatRequest{
        Name: "Ransomware-X",
        Severity: pb.Severity_CRITICAL,
    })
}

Example 2: Reading Metadata in Python Server

import grpc
from grpc_interceptor import ServerInterceptor

class MetadataInterceptor(ServerInterceptor):
    def intercept(self, method, request, context, method_name):
        # Read incoming metadata
        auth = dict(context.invocation_metadata())
        
        token = auth.get("authorization", "")
        correlation_id = auth.get("correlation-id", "")
        region = auth.get("x-region", "default")
        
        print(f"Correlation ID: {correlation_id}")
        print(f"Region: {region}")
        
        # Validate auth
        if not token.startswith("Bearer "):
            context.abort(
                grpc.StatusCode.UNAUTHENTICATED,
                "Missing or invalid token",
            )
        
        # Set response headers
        context.send_initial_metadata([
            ("x-request-id", generate_request_id()),
        ])
        
        return method(request, context)

# Server
server = grpc.server(
    grpc.insecure_server(),
    interceptors=[MetadataInterceptor()],
)

Example 3: Propagation Across Services

const grpc = require('@grpc/grpc-js');
const { Metadata } = grpc;

class MetadataPropagator {
  constructor() {
    this._metadata = new Metadata();
  }
  
  // Called by interceptor on incoming request
  captureMetadata(call) {
    const incoming = call.metadata.getMap();
    this._metadata.set('authorization', incoming['authorization'] || '');
    this._metadata.set('correlation-id', incoming['correlation-id'] || uuid());
    this._metadata.set('x-service', incoming['x-service'] || 'unknown');
  }
  
  // Apply to outgoing calls to downstream services
  attachMetadata() {
    const md = new Metadata();
    for (const [key, values] of this._metadata.getMap()) {
      md.set(key, values);
    }
    md.set('x-hop', this._metadata.get('x-hop') || '1');
    return md;
  }
}

// Usage in a service that calls downstream
async function handleRequest(call, callback) {
  const propagator = new MetadataPropagator();
  propagator.captureMetadata(call);
  
  // Call downstream service with propagated metadata
  const downstreamClient = new DownstreamClient(
    'localhost:50052',
    grpc.credentials.createInsecure(),
  );
  
  downstreamClient.ProcessData(
    request,
    propagator.attachMetadata(),
    (err, response) => {
      if (err) callback(err);
      else callback(null, response);
    },
  );
}

Common Mistakes

  1. Putting large data in metadata — metadata is limited to 8KB total. Store large data in protobuf messages, not metadata.
  2. Forgetting to propagate metadata — when service A calls service B, metadata from the original request (auth, correlation ID) must be forwarded manually.
  3. Using mutable metadata across concurrent calls — metadata objects are not thread-safe. Create new metadata for each outgoing call instead of reusing.
  4. Not handling binary metadata — binary metadata keys must end with -bin and values must be base64-encoded.
  5. Ignoring metadata size limits — gRPC limits metadata to 8KB by default. Configure max metadata size for large headers.

Practice Questions

  1. What is the difference between gRPC metadata and protobuf message fields?
  2. How do you read incoming metadata in a gRPC server interceptor?
  3. Why should you propagate metadata across service boundaries?
  4. What is the 8KB metadata limit and what happens if you exceed it?
  5. How do you send binary data in metadata?

Challenge: Build a metadata propagation system that automatically forwards authorization, correlation ID, and tracing headers across a chain of 3 gRPC services, adding a hop counter and service name at each step.

Mini Project

Create a gRPC interceptor library that handles metadata propagation across a service mesh: capture incoming metadata, validate auth tokens, add tracing spans, forward correlation IDs to downstream calls, and add response trailers with request timing and service info.

FAQ

How is gRPC metadata different from HTTP headers?

gRPC metadata uses the same HTTP/2 header mechanism but has strict conventions: keys are lowercase, binary keys end with -bin, and values are byte strings.

Can I modify metadata after sending the initial request?

No. Outgoing metadata is sent with the initial request. For responses, headers are sent before the response body and trailers after.

What metadata keys are reserved?

Keys starting with grpc- are reserved. Common conventions: authorization, x-request-id, x-correlation-id, x-forwarded-for.

How do I handle metadata in streaming calls?

Metadata is sent once at the start of the stream. For bidirectional streaming, the initial metadata carries the connection setup info.

Should I put auth tokens in metadata?

Yes. Auth tokens are the most common use case for metadata. Pass them in the 'authorization' key as Bearer tokens.

What's Next

Learn about gRPC interceptors for middleware

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro