Repository: cloudevents/sdk-go
Pull request #372 (2020-03-11 23:05:26)

Rework how responses are supported in Client
This is a big breaking change to the Client interface, highlights:

- Removal of event.EventResponse
- Overloading of the returned error, it is now event.Result which can signal for protocols to do something special to Ack or Nack a request of response.
    - error and event.Result are equivalent in a receiver method.

The full receive method signature is changing from:

```
func(context.Context, event.Event, *event.EventResponse) error
```

to:

```
func(context.Context, event.Event) (*event.Event, event.Result)
```


This change brings Client much more inline with what the protocol interfaces are. The next step will be to remove the transport.Transport interface in favor of something more direct.



n3wscott: > I think you should also implement the check when the `Receiver` is set up, to return an error when the used protocol doesn't support request/response semantic

that was todays task to work out. It is hard at the moment because the transport interface is too general for the new interfaces.
slinkydeveloper: >that was todays task to work out. It is hard at the moment because the transport interface is too general for the new interfaces.

Ok we figure it out today


Patch:
diff --git a/alias.go b/alias.go
index 296024a..48cd439 100644
--- a/alias.go
+++ b/alias.go
@@ -8,6 +8,7 @@ import (
 	"github.com/cloudevents/sdk-go/pkg/context"
 	"github.com/cloudevents/sdk-go/pkg/event"
 	"github.com/cloudevents/sdk-go/pkg/observability"
+	"github.com/cloudevents/sdk-go/pkg/transport"
 	"github.com/cloudevents/sdk-go/pkg/transport/http"
 	"github.com/cloudevents/sdk-go/pkg/types"
 )
@@ -16,12 +17,10 @@ import (
 
 type ClientOption client.Option
 type Client = client.Client
-type ConvertFn = client.ConvertFn
 
 // Event
 
 type Event = event.Event
-type EventResponse = event.EventResponse
 
 // Context
 
@@ -85,7 +84,10 @@ var (
 
 	// Event Creation
 
-	NewEvent = event.New
+	NewEvent  = event.New
+	NewResult = transport.NewResult
+
+	NewHTTPResponse = http.NewResult
 
 	// Tracing
 
@@ -113,7 +115,7 @@ var (
 	// HTTP Protocol Options
 
 	WithTarget             = http.WithTarget
-	WitHHeader             = http.WithHeader
+	WithHeader             = http.WithHeader
 	WithShutdownTimeout    = http.WithShutdownTimeout
 	WithEncoding           = http.WithEncoding
 	WithStructuredEncoding = http.WithStructuredEncoding
diff --git a/cmd/samples/http/raw/main.go b/cmd/samples/http/raw/main.go
index 9ef4493..1655949 100644
--- a/cmd/samples/http/raw/main.go
+++ b/cmd/samples/http/raw/main.go
@@ -21,5 +21,6 @@ func (raw *RawHTTP) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 }
 
 func main() {
-	log.Println(http.ListenAndServe(":8282", &RawHTTP{}))
+	log.Println("listening on :8080")
+	log.Println(http.ListenAndServe(":8080", &RawHTTP{}))
 }
diff --git a/cmd/samples/http/responder/main.go b/cmd/samples/http/responder/main.go
index 4f285bd..4ebcf66 100644
--- a/cmd/samples/http/responder/main.go
+++ b/cmd/samples/http/responder/main.go
@@ -30,7 +30,7 @@ type Example struct {
 	Message  string `json:"message"`
 }
 
-func gotEvent(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error {
+func gotEvent(ctx context.Context, event cloudevents.Event) (*cloudevents.Event, error) {
 	fmt.Printf("Got Event Context: %+v\n", event.Context)
 	data := &Example{}
 	if err := event.DataAs(data); err != nil {
@@ -50,11 +50,10 @@ func gotEvent(ctx context.Context, event cloudevents.Event, resp *cloudevents.Ev
 				Message:  "mod 3!",
 			},
 		}
-		resp.RespondWith(200, &r)
-		return nil
+		return &r, nil
 	}
 
-	return nil
+	return nil, nil
 }
 
 func _main(args []string, env envConfig) int {
diff --git a/cmd/samples/httpb/responder/main.go b/cmd/samples/httpb/responder/main.go
index 69be799..e3d17e9 100644
--- a/cmd/samples/httpb/responder/main.go
+++ b/cmd/samples/httpb/responder/main.go
@@ -4,11 +4,13 @@ import (
 	"context"
 	"fmt"
 	"log"
+	"net/http"
 	"os"
 
-	"github.com/cloudevents/sdk-go/pkg/transport/http"
+	"github.com/cloudevents/sdk-go/pkg/transport"
 
 	cloudevents "github.com/cloudevents/sdk-go"
+	"github.com/cloudevents/sdk-go/pkg/event"
 	"github.com/kelseyhightower/envconfig"
 )
 
@@ -31,7 +33,7 @@ func main() {
 		log.Fatalf("failed to create protocol: %s", err.Error())
 	}
 
-	t, err := http.New(p)
+	t, err := cloudevents.NewHTTPTransport(p)
 	if err != nil {
 		log.Fatalf("failed to create transport, %v", err)
 	}
@@ -39,6 +41,7 @@ func main() {
 	c, err := cloudevents.NewClient(t,
 		cloudevents.WithUUIDs(),
 		cloudevents.WithTimeNow(),
+		cloudevents.WithoutTracePropagation(),
 	)
 	if err != nil {
 		log.Fatalf("failed to create client: %s", err.Error())
@@ -50,9 +53,8 @@ func main() {
 	}
 }
 
-func gotEvent(ctx context.Context, event cloudevents.Event, resp *cloudevents.EventResponse) error {
+func gotEvent(ctx context.Context, event cloudevents.Event) (*event.Event, transport.Result) {
 	fmt.Printf("Got Event: %+v\n", event)
 
-	resp.RespondWith(200, &event)
-	return nil
+	return &event, cloudevents.NewHTTPResponse(http.StatusAccepted, "accept")
 }
diff --git a/cmd/samples/nats/receiver/main.go b/cmd/samples/nats/receiver/main.go
index d4518c0..23292ba 100644
--- a/cmd/samples/nats/receiver/main.go
+++ b/cmd/samples/nats/receiver/main.go
@@ -49,7 +49,7 @@ type Example struct {
 	Message  string `json:"message"`
 }
 
-func receive(ctx context.Context, event event.Event, resp *event.EventResponse) error {
+func receive(ctx context.Context, event event.Event) error {
 	fmt.Printf("Got Event Context: %+v\n", event.Context)
 
 	data := &Example{}
diff --git a/go.sum b/go.sum
index fd48c24..98cb976 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,6 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc=
 contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
diff --git a/pkg/client/client.go b/pkg/client/client.go
index 2fe89a2..5b17cf1 100644
--- a/pkg/client/client.go
+++ b/pkg/client/client.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"sync"
 
-	"github.com/cloudevents/sdk-go/pkg/binding"
 	"github.com/cloudevents/sdk-go/pkg/event"
 	"github.com/cloudevents/sdk-go/pkg/extensions"
 	"github.com/cloudevents/sdk-go/pkg/observability"
@@ -31,17 +30,15 @@ type Client interface {
 	// * func()
 	// * func() error
 	// * func(context.Context)
-	// * func(context.Context) error
+	// * func(context.Context) transport.Result
 	// * func(event.Event)
-	// * func(event.Event) error
+	// * func(event.Event) transport.Result
 	// * func(context.Context, event.Event)
-	// * func(context.Context, event.Event) error
-	// * func(event.Event, *event.EventResponse)
-	// * func(event.Event, *event.EventResponse) error
-	// * func(context.Context, event.Event, *event.EventResponse)
-	// * func(context.Context, event.Event, *event.EventResponse) error
-	// Note: if fn returns an error, it is treated as a critical and
-	// EventResponse will not be processed.
+	// * func(context.Context, event.Event) transport.Result
+	// * func(event.Event) *event.Event
+	// * func(event.Event) (*event.Event, transport.Result)
+	// * func(context.Context, event.Event) *event.Event
+	// * func(context.Context, event.Event) (*event.Event, transport.Result)
 	StartReceiver(ctx context.Context, fn interface{}) error
 }
 
@@ -85,8 +82,6 @@ type ceClient struct {
 	transport transport.Transport
 	fn        *receiverFn
 
-	convertFn ConvertFn
-
 	receiverMu        sync.Mutex
 	eventDefaulterFns []EventDefaulter
 
@@ -196,7 +191,7 @@ func (c *ceClient) obsRequest(ctx context.Context, event event.Event) (*event.Ev
 }
 
 // Delivery is called from from the transport on event delivery.
-func (c *ceClient) Delivery(ctx context.Context, e event.Event, resp *event.EventResponse) error {
+func (c *ceClient) Delivery(ctx context.Context, e event.Event) (*event.Event, transport.Result) {
 	ctx, r := observability.NewReporter(ctx, reportReceive)
 
 	var span *trace.Span
@@ -213,32 +208,32 @@ func (c *ceClient) Delivery(ctx context.Context, e event.Event, resp *event.Even
 		span.AddAttributes(eventTraceAttributes(e.Context)...)
 	}
 
-	err := c.obsDelivery(ctx, e, resp)
-	if err != nil {
+	resp, result := c.obsDelivery(ctx, e)
+	if result != nil { // TODO: test result for Ack/Nack
 		r.Error()
 	} else {
 		r.OK()
 	}
-	return err
+	return resp, result
 }
 
-func (c *ceClient) obsDelivery(ctx context.Context, e event.Event, resp *event.EventResponse) error {
+func (c *ceClient) obsDelivery(ctx context.Context, e event.Event) (*event.Event, transport.Result) {
 	if c.fn != nil {
-		err := c.fn.invoke(ctx, e, resp)
+		resp, err := c.fn.invoke(ctx, e)
 
 		// Apply the defaulter chain to the outgoing event.
-		if err == nil && resp != nil && resp.Event != nil && len(c.eventDefaulterFns) > 0 {
+		if err == nil && resp != nil && len(c.eventDefaulterFns) > 0 {
 			for _, fn := range c.eventDefaulterFns {
-				*resp.Event = fn(ctx, *resp.Event)
+				*resp = fn(ctx, *resp)
 			}
 			// Validate the event conforms to the CloudEvents Spec.
-			if err := resp.Event.Validate(); err != nil {
-				return fmt.Errorf("cloudevent validation failed on response event: %v", err)
+			if verr := resp.Validate(); verr != nil {
+				return nil, fmt.Errorf("cloudevent validation failed on response event: %v, %w", verr, err)
 			}
 		}
-		return err
+		return resp, err
 	}
-	return nil
+	return nil, nil
 }
 
 // StartReceiver sets up the given fn to handle Receive.
@@ -275,11 +270,3 @@ func (c *ceClient) applyOptions(opts ...Option) error {
 	}
 	return nil
 }
-
-// Convert implements transport Converter.Convert.
-func (c *ceClient) Convert(ctx context.Context, m binding.Message, err error) (*event.Event, error) {
-	if c.convertFn != nil {
-		return c.convertFn(ctx, m, err)
-	}
-	return nil, err
-}
diff --git a/pkg/client/receiver.go b/pkg/client/receiver.go
index 9517431..fe5cafb 100644
--- a/pkg/client/receiver.go
+++ b/pkg/client/receiver.go
@@ -6,41 +6,35 @@ import (
 	"fmt"
 	"reflect"
 
-	"github.com/cloudevents/sdk-go/pkg/binding"
 	"github.com/cloudevents/sdk-go/pkg/event"
+	"github.com/cloudevents/sdk-go/pkg/transport"
 )
 
 // Receive is the signature of a fn to be invoked for incoming cloudevents.
-// If fn returns an error, EventResponse will not be considered by the client or
-// or transport.
-// This is just an FYI:
-type ReceiveFull func(context.Context, event.Event, *event.EventResponse) error
+type ReceiveFull func(context.Context, event.Event) transport.Result
 
 type receiverFn struct {
 	numIn   int
+	numOut  int
 	fnValue reflect.Value
 
-	hasContextIn       bool
-	hasEventIn         bool
-	hasEventResponseIn bool
+	hasContextIn bool
+	hasEventIn   bool
 
-	hasErrorOut bool
+	hasEventOut  bool
+	hasResultOut bool
 }
 
-// ConvertFn defines the signature the client expects to enable conversion
-// delegation.
-type ConvertFn func(context.Context, binding.Message, error) (*event.Event, error)
-
 const (
-	inParamUsage  = "expected a function taking either no parameters, one or more of (context.Context, event.Event, *event.EventResponse) ordered"
-	outParamUsage = "expected a function returning either nothing or an error"
+	inParamUsage  = "expected a function taking either no parameters, one or more of (context.Context, event.Event) ordered"
+	outParamUsage = "expected a function returning one or mode of (*event.Event, transport.Result) ordered"
 )
 
 var (
-	contextType       = reflect.TypeOf((*context.Context)(nil)).Elem()
-	eventType         = reflect.TypeOf((*event.Event)(nil)).Elem()
-	eventResponseType = reflect.TypeOf((*event.EventResponse)(nil)) // want the ptr type
-	errorType         = reflect.TypeOf((*error)(nil)).Elem()
+	contextType  = reflect.TypeOf((*context.Context)(nil)).Elem()
+	eventType    = reflect.TypeOf((*event.Event)(nil)).Elem()
+	eventPtrType = reflect.TypeOf((*event.Event)(nil)) // want the ptr type
+	resultType   = reflect.TypeOf((*transport.Result)(nil)).Elem()
 )
 
 // receiver creates a receiverFn wrapper class that is used by the client to
@@ -49,15 +43,15 @@ var (
 // * func()
 // * func() error
 // * func(context.Context)
-// * func(context.Context) error
+// * func(context.Context) transport.Result
 // * func(event.Event)
-// * func(event.Event) error
+// * func(event.Event) transport.Result
 // * func(context.Context, event.Event)
-// * func(context.Context, event.Event) error
-// * func(event.Event, *event.EventResponse)
-// * func(event.Event, *event.EventResponse) error
-// * func(context.Context, event.Event, *event.EventResponse)
-// * func(context.Context, event.Event, *event.EventResponse) error
+// * func(context.Context, event.Event) transport.Result
+// * func(event.Event) *event.Event
+// * func(event.Event) (*event.Event, transport.Result)
+// * func(context.Context, event.Event, *event.Event
+// * func(context.Context, event.Event) (*event.Event, transport.Result)
 //
 func receiver(fn interface{}) (*receiverFn, error) {
 	fnType := reflect.TypeOf(fn)
@@ -68,7 +62,9 @@ func receiver(fn interface{}) (*receiverFn, error) {
 	r := &receiverFn{
 		fnValue: reflect.ValueOf(fn),
 		numIn:   fnType.NumIn(),
+		numOut:  fnType.NumOut(),
 	}
+
 	if err := r.validate(fnType); err != nil {
 		return nil, err
 	}
@@ -76,7 +72,7 @@ func receiver(fn interface{}) (*receiverFn, error) {
 	return r, nil
 }
 
-func (r *receiverFn) invoke(ctx context.Context, event event.Event, resp *event.EventResponse) error {
+func (r *receiverFn) invoke(ctx context.Context, e event.Event) (*event.Event, transport.Result) {
 	args := make([]reflect.Value, 0, r.numIn)
 
 	if r.numIn > 0 {
@@ -84,68 +80,53 @@ func (r *receiverFn) invoke(ctx context.Context, event event.Event, resp *event.
 			args = append(args, reflect.ValueOf(ctx))
 		}
 		if r.hasEventIn {
-			args = append(args, reflect.ValueOf(event))
-		}
-		if r.hasEventResponseIn {
-			args = append(args, reflect.ValueOf(resp))
+			args = append(args, reflect.ValueOf(e))
 		}
 	}
 	v := r.fnValue.Call(args)
-	if r.hasErrorOut && len(v) >= 1 {
-		if err, ok := v[0].Interface().(error); ok {
-			return err
+	var respOut transport.Result
+	var eOut *event.Event
+	if r.numOut > 0 {
+		i := 0
+		if r.hasEventOut {
+			if eo, ok := v[i].Interface().(*event.Event); ok {
+				eOut = eo
+			}
+			i++ // <-- note, need to inc i.
+		}
+		if r.hasResultOut {
+			if resp, ok := v[i].Interface().(transport.Result); ok {
+				respOut = resp
+			}
 		}
 	}
-	return nil
+	return eOut, respOut
 }
 
 // Verifies that the inputs to a function have a valid signature
 // Valid input is to be [0, all] of
-// context.Context, event.Event, *event.EventResponse in this order.
+// context.Context, event.Event in this order.
 func (r *receiverFn) validateInParamSignature(fnType reflect.Type) error {
 	r.hasContextIn = false
 	r.hasEventIn = false
-	r.hasEventResponseIn = false
 
 	switch fnType.NumIn() {
-	case 3:
-		// has to be event.Event, *event.EventResponse
-		if !fnType.In(2).ConvertibleTo(eventResponseType) {
-			return fmt.Errorf("%s; cannot convert parameter 2 from %s to *event.EventResponse", inParamUsage, fnType.In(2))
-		} else {
-			r.hasEventResponseIn = true
-		}
-		fallthrough
 	case 2:
-		// can be event.Event or *event.EventResponse
-		if !fnType.In(1).ConvertibleTo(eventResponseType) {
-			if !fnType.In(1).ConvertibleTo(eventType) {
-				return fmt.Errorf("%s; cannot convert parameter 1 from %s to event.Event or *event.EventResponse", inParamUsage, fnType.In(1))
-			} else {
-				r.hasEventIn = true
-			}
-		} else if r.hasEventResponseIn {
-			return fmt.Errorf("%s; duplicate parameter of type *event.EventResponse", inParamUsage)
+		// has to be (context.Context, event.Event)
+		if !fnType.In(1).ConvertibleTo(eventType) {
+			return fmt.Errorf("%s; cannot convert parameter 2 from %s to event.Event", inParamUsage, fnType.In(1))
 		} else {
-			r.hasEventResponseIn = true
+			r.hasEventIn = true
 		}
 		fallthrough
 	case 1:
 		if !fnType.In(0).ConvertibleTo(contextType) {
-			if !fnType.In(0).ConvertibleTo(eventResponseType) {
-				if !fnType.In(0).ConvertibleTo(eventType) {
-					return fmt.Errorf("%s; cannot convert parameter 0 from %s to context.Context, event.Event or *event.EventResponse", inParamUsage, fnType.In(0))
-				} else if r.hasEventIn {
-					return fmt.Errorf("%s; duplicate parameter of type event.Event", inParamUsage)
-				} else {
-					r.hasEventIn = true
-				}
-			} else if r.hasEventResponseIn {
-				return fmt.Errorf("%s; duplicate parameter of type *event.EventResponse", inParamUsage)
+			if !fnType.In(0).ConvertibleTo(eventType) {
+				return fmt.Errorf("%s; cannot convert parameter 1 from %s to context.Context or event.Event", inParamUsage, fnType.In(0))
 			} else if r.hasEventIn {
-				return fmt.Errorf("%s; out of order parameter 0 for %s", inParamUsage, fnType.In(1))
+				return fmt.Errorf("%s; duplicate parameter of type event.Event", inParamUsage)
 			} else {
-				r.hasEventResponseIn = true
+				r.hasEventIn = true
 			}
 		} else {
 			r.hasContextIn = true
@@ -153,24 +134,39 @@ func (r *receiverFn) validateInParamSignature(fnType reflect.Type) error {
 		fallthrough
 	case 0:
 		return nil
+
 	default:
 		return fmt.Errorf("%s; function has too many parameters (%d)", inParamUsage, fnType.NumIn())
 	}
 }
 
 // Verifies that the outputs of a function have a valid signature
-// Valid output signatures:
-// (), (error)
+// Valid output signatures to be [0, all] of
+// *event.Event, transport.Result in this order
 func (r *receiverFn) validateOutParamSignature(fnType reflect.Type) error {
-	r.hasErrorOut = false
+	r.hasEventOut = false
+	r.hasResultOut = false
+
 	switch fnType.NumOut() {
+	case 2:
+		// has to be (*event.Event, transport.Result)
+		if !fnType.Out(1).ConvertibleTo(resultType) {
+			return fmt.Errorf("%s; cannot convert parameter 2 from %s to event.Response", outParamUsage, fnType.Out(1))
+		} else {
+			r.hasResultOut = true
+		}
+		fallthrough
 	case 1:
-		paramNo := fnType.NumOut() - 1
-		paramType := fnType.Out(paramNo)
-		if !paramType.ConvertibleTo(errorType) {
-			return fmt.Errorf("%s; cannot convert return type %d from %s to error", outParamUsage, paramNo, paramType)
+		if !fnType.Out(0).ConvertibleTo(resultType) {
+			if !fnType.Out(0).ConvertibleTo(eventPtrType) {
+				return fmt.Errorf("%s; cannot convert parameter 1 from %s to *event.Event or transport.Result", outParamUsage, fnType.Out(0))
+			} else {
+				r.hasEventOut = true
+			}
+		} else if r.hasResultOut {
+			return fmt.Errorf("%s; duplicate parameter of type event.Response", outParamUsage)
 		} else {
-			r.hasErrorOut = true
+			r.hasResultOut = true
 		}
 		fallthrough
 	case 0:
diff --git a/pkg/event/event.go b/pkg/event/event.go
index 52a9d9f..0dbc062 100644
--- a/pkg/event/event.go
+++ b/pkg/event/event.go
@@ -82,8 +82,6 @@ func (e Event) Validate() error {
 		return err
 	}
 
-	// TODO: validate data.
-
 	return nil
 }
 
diff --git a/pkg/event/event_response.go b/pkg/event/event_response.go
deleted file mode 100644
index 23b7704..0000000
--- a/pkg/event/event_response.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package event
-
-// EventResponse represents the canonical representation of a Response to a
-// CloudEvent from a receiver. Response implementation is Transport dependent.
-// Deprecated: handle the protocol directly.
-type EventResponse struct {
-	// Deprecated: handle the protocol directly.
-	Status int
-	// Deprecated: handle the protocol directly.
-	Event *Event
-	// Deprecated: handle the protocol directly.
-	Reason string
-	// Context is transport specific struct to allow for controlling transport
-	// response details.
-	// For example, see http.TransportResponseContext.
-	// Deprecated: handle the protocol directly.
-	Context interface{}
-}
-
-// RespondWith sets up the instance of EventResponse to be set with status and
-// an event. Response implementation is Transport dependent.
-func (e *EventResponse) RespondWith(status int, event *Event) {
-	if e == nil {
-		// if nil, response not supported
-		return
-	}
-	e.Status = status
-	if event != nil {
-		e.Event = event
-	}
-}
-
-// Error sets the instance of EventResponse to be set with an error code and
-// reason string. Response implementation is Transport dependent.
-func (e *EventResponse) Error(status int, reason string) {
-	if e == nil {
-		// if nil, response not supported
-		return
-	}
-	e.Status = status
-	e.Reason = reason
-}
diff --git a/pkg/transport/bindings/transport.go b/pkg/transport/bindings/transport.go
index 042f592..f97cc36 100644
--- a/pkg/transport/bindings/transport.go
+++ b/pkg/transport/bindings/transport.go
@@ -116,23 +116,22 @@ func (t *BindingTransport) handle(ctx context.Context, m binding.Message, respFn
 	}()
 
 	if t.handler == nil {
-		return
+		return nil
 	}
 
 	e, err := binding.ToEvent(ctx, m, nil)
 	if err != nil {
 		return err
 	}
-	eventResp := event.EventResponse{}
-	if err := t.handler.Delivery(ctx, *e, &eventResp); err != nil {
-		return err
-	}
 
-	if eventResp.Event != nil {
-		// TODO: this does not give control over the http response code at the moment.
-		if respFn != nil {
-			return respFn(ctx, (*binding.EventMessage)(eventResp.Event))
+	resp, result := t.handler.Delivery(ctx, *e)
+	if respFn != nil {
+		var rm binding.Message
+		if resp != nil {
+			rm = (*binding.EventMessage)(resp)
 		}
+
+		return respFn(ctx, rm, result)
 	}
 
 	return nil
diff --git a/pkg/transport/http/message_write_responsewriter.go b/pkg/transport/http/message_write_responsewriter.go
index 3a2f24b..6056385 100644
--- a/pkg/transport/http/message_write_responsewriter.go
+++ b/pkg/transport/http/message_write_responsewriter.go
@@ -15,15 +15,17 @@ import (
 
 // Write out to the the provided httpResponseWriter with the message m.
 // Using context you can tweak the encoding processing (more details on binding.Write documentation).
-func WriteResponseWriter(ctx context.Context, m binding.Message, rw http.ResponseWriter, transformers binding.TransformerFactories) error {
-	structuredWriter := &httpResponseWriterEncoder{rw: rw}
-	binaryWriter := &httpResponseWriterEncoder{rw: rw}
+func WriteResponseWriter(ctx context.Context, m binding.Message, status int, rw http.ResponseWriter, transformers binding.TransformerFactories) error {
+	if status < 200 || status >= 600 {
+		status = http.StatusOK
+	}
+	writer := &httpResponseWriterEncoder{rw: rw, status: status}
 
 	_, err := binding.Write(
 		ctx,
 		m,
-		structuredWriter,
-		binaryWriter,
+		writer,
+		writer,
 		transformers,
 	)
 	return err
@@ -48,6 +50,10 @@ func (b *httpResponseWriterEncoder) End() error {
 }
 
 func (b *httpResponseWriterEncoder) SetData(reader io.Reader) error {
+	// Finalize the headers.
+	b.rw.WriteHeader(b.status)
+
+	// Write body.
 	body, err := ioutil.ReadAll(reader)
 	if err != nil {
 		return err
diff --git a/pkg/transport/http/options.go b/pkg/transport/http/options.go
index b8e50ff..af9f8fe 100644
--- a/pkg/transport/http/options.go
+++ b/pkg/transport/http/options.go
@@ -56,7 +56,9 @@ func WithHeader(key, value string) ProtocolOption {
 		key = strings.TrimSpace(key)
 		if key != "" {
 			if p.RequestTemplate == nil {
-				p.RequestTemplate = &nethttp.Request{}
+				p.RequestTemplate = &nethttp.Request{
+					Method: nethttp.MethodPost,
+				}
 			}
 			if p.RequestTemplate.Header == nil {
 				p.RequestTemplate.Header = nethttp.Header{}
diff --git a/pkg/transport/http/protocol.go b/pkg/transport/http/protocol.go
index 7c2907e..1935976 100644
--- a/pkg/transport/http/protocol.go
+++ b/pkg/transport/http/protocol.go
@@ -3,12 +3,13 @@ package http
 import (
 	"context"
 	"fmt"
-	"github.com/cloudevents/sdk-go/pkg/transport"
 	"io"
 	"net/http"
 	"net/url"
 	"time"
 
+	"github.com/cloudevents/sdk-go/pkg/transport"
+
 	"github.com/cloudevents/sdk-go/pkg/binding"
 	cecontext "github.com/cloudevents/sdk-go/pkg/context"
 )
@@ -138,7 +139,7 @@ func (p *Protocol) Receive(ctx context.Context) (binding.Message, error) {
 	// No-op the response.
 	defer func() {
 		if fn != nil {
-			_ = fn(ctx, nil)
+			_ = fn(ctx, nil, nil)
 		}
 	}()
 	return msg, err
@@ -177,15 +178,28 @@ func (p *Protocol) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 		return nil
 	}
 
-	var fn transport.ResponseFn = func(ctx context.Context, resp binding.Message) error {
+	var fn transport.ResponseFn = func(ctx context.Context, resp binding.Message, er transport.Result) error {
 		if resp != nil {
-			return WriteResponseWriter(ctx, resp, rw, p.transformers)
+			status := http.StatusOK
+			if er != nil {
+				var result *Result
+				if transport.ResultAs(er, &result) {
+					if result.Status > 100 && result.Status < 600 {
+						status = result.Status
+					}
+				}
+			}
+
+			err := WriteResponseWriter(ctx, resp, status, rw, p.transformers)
+			return resp.Finish(err)
 		}
+
 		return nil
 	}
 
 	p.incoming <- msgErr{msg: m, respFn: fn, err: err} // Send to Respond()
 	if err = <-done; err != nil {
+		fmt.Println("attempting to write an error out on response writer:", err)
 		http.Error(rw, fmt.Sprintf("cannot forward CloudEvent: %v", err), http.StatusInternalServerError)
 	}
 }
diff --git a/pkg/transport/http/response.go b/pkg/transport/http/response.go
new file mode 100644
index 0000000..aaef7cd
--- /dev/null
+++ b/pkg/transport/http/response.go
@@ -0,0 +1,44 @@
+package http
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/cloudevents/sdk-go/pkg/transport"
+)
+
+// NewResult returns a fully populated http Result that should be used as
+// a transport.Result.
+func NewResult(status int, messageFmt string, args ...interface{}) transport.Result {
+	return &Result{
+		Status: status,
+		Format: messageFmt,
+		Args:   args,
+	}
+}
+
+// Result wraps the fields required to make adjustments for http Responses.
+type Result struct {
+	Status int
+	Format string
+	Args   []interface{}
+}
+
+// make sure Result implements error.
+var _ error = (*Result)(nil)
+
+// Is returns if the target error is a Result type checking target.
+func (e *Result) Is(target error) bool {
+	if _, ok := target.(*Result); ok {
+		return true
+	}
+	// Allow for wrapped errors.
+	err := fmt.Errorf(e.Format, e.Args...)
+	return errors.Is(err, target)
+}
+
+// Error returns the string that is formed by using the format string with the
+// provided args.
+func (e *Result) Error() string {
+	return fmt.Sprintf(e.Format, e.Args...)
+}
diff --git a/pkg/transport/responder.go b/pkg/transport/responder.go
index 202236a..622e424 100644
--- a/pkg/transport/responder.go
+++ b/pkg/transport/responder.go
@@ -8,7 +8,7 @@ import (
 
 // ResponseFn is the function callback provided from Responder.Respond to allow
 // for a receiver to "reply" to a message it receives.
-type ResponseFn func(ctx context.Context, m binding.Message) error
+type ResponseFn func(ctx context.Context, m binding.Message, r Result) error
 
 // Responder receives messages and is given a callback to respond.
 type Responder interface {
diff --git a/pkg/transport/result.go b/pkg/transport/result.go
new file mode 100644
index 0000000..7f926fe
--- /dev/null
+++ b/pkg/transport/result.go
@@ -0,0 +1,39 @@
+package transport
+
+import (
+	"errors"
+	"fmt"
+)
+
+// Result leverages go's 1.13 error wrapping.
+type Result error
+
+// Is reports whether any error in err's chain matches target.
+//
+// The chain consists of err itself followed by the sequence of errors obtained by
+// repeatedly calling Unwrap.
+//
+// An error is considered to match a target if it is equal to that target or if
+// it implements a method Is(error) bool such that Is(target) returns true.
+// (text from errors/wrap.go)
+var ResultIs = errors.Is
+
+// As finds the first error in err's chain that matches target, and if so, sets
+// target to that error value and returns true.
+//
+// The chain consists of err itself followed by the sequence of errors obtained by
+// repeatedly calling Unwrap.
+//
+// An error matches target if the error's concrete value is assignable to the value
+// pointed to by target, or if the error has a method As(interface{}) bool such that
+// As(target) returns true. In the latter case, the As method is responsible for
+// setting target.
+//
+// As will panic if target is not a non-nil pointer to either a type that implements
+// error, or to any interface type. As returns false if err is nil.
+// (text from errors/wrap.go)
+var ResultAs = errors.As
+
+func NewResult(messageFmt string, args ...interface{}) Result {
+	return fmt.Errorf(messageFmt, args...) // TODO: look at adding Ack/Nak support.
+}
diff --git a/pkg/transport/transport.go b/pkg/transport/transport.go
index 2f12598..01ac16d 100644
--- a/pkg/transport/transport.go
+++ b/pkg/transport/transport.go
@@ -28,13 +28,13 @@ type Transport interface {
 // Deprecated: use bindings.
 type Delivery interface {
 	// Deprecated: use bindings.
-	Delivery(context.Context, event.Event, *event.EventResponse) error
+	Delivery(context.Context, event.Event) (*event.Event, Result)
 }
 
 // DeliveryFunc wraps a function as a Receiver object.
-type DeliveryFunc func(ctx context.Context, e event.Event, er *event.EventResponse) error
+type DeliveryFunc func(ctx context.Context, e event.Event) (*event.Event, error)
 
 // Receive implements Receiver.Receive
-func (f DeliveryFunc) Receive(ctx context.Context, e event.Event, er *event.EventResponse) error {
-	return f(ctx, e, er)
+func (f DeliveryFunc) Receive(ctx context.Context, e event.Event) (*event.Event, error) {
+	return f(ctx, e)
 }


Test patch:
diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go
index 663c2f4..b1d7f5c 100644
--- a/pkg/client/client_test.go
+++ b/pkg/client/client_test.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/cloudevents/sdk-go/pkg/client"
 	"github.com/cloudevents/sdk-go/pkg/event"
+	"github.com/cloudevents/sdk-go/pkg/transport"
 	cehttp "github.com/cloudevents/sdk-go/pkg/transport/http"
 	"github.com/cloudevents/sdk-go/pkg/types"
 )
@@ -404,7 +405,7 @@ func TestClientReceive(t *testing.T) {
 
 				ctx, cancel := context.WithCancel(context.TODO())
 				go func() {
-					err = c.StartReceiver(ctx, func(ctx context.Context, event event.Event, resp *event.EventResponse) error {
+					err = c.StartReceiver(ctx, func(ctx context.Context, event event.Event) error {
 						go func() {
 							events <- event
 						}()
@@ -533,11 +534,11 @@ func TestTracedClientReceive(t *testing.T) {
 
 			ctx, cancel := context.WithCancel(context.TODO())
 			go func() {
-				err = c.StartReceiver(ctx, func(ctx context.Context, event event.Event, resp *event.EventResponse) error {
+				err = c.StartReceiver(ctx, func(ctx context.Context, event event.Event) (*event.Event, transport.Result) {
 					go func() {
 						spanContexts <- trace.FromContext(ctx).SpanContext()
 					}()
-					return nil
+					return nil, nil
 				})
 				if err != nil {
 					t.Errorf("failed to start receiver %s", err.Error())
diff --git a/pkg/client/receiver_test.go b/pkg/client/receiver_test.go
index a77cb78..5e35800 100644
--- a/pkg/client/receiver_test.go
+++ b/pkg/client/receiver_test.go
@@ -6,28 +6,38 @@ import (
 	"testing"
 
 	"github.com/cloudevents/sdk-go/pkg/event"
+	"github.com/cloudevents/sdk-go/pkg/transport"
 
 	"github.com/google/go-cmp/cmp"
 )
 
 func TestReceiverFnValidTypes(t *testing.T) {
 	for name, fn := range map[string]interface{}{
-		"no in, no out":                           func() {},
-		"no in, error out":                        func() error { return nil },
-		"ctx in, no out":                          func(context.Context) {},
-		"ctx, Event in, no out":                   func(context.Context, event.Event) {},
-		"ctx, EventResponse in, no out":           func(context.Context, *event.EventResponse) {},
-		"ctx, Event, EventResponse in, no out":    func(context.Context, event.Event, *event.EventResponse) {},
-		"ctx in, error out":                       func(context.Context) error { return nil },
-		"ctx, Event in, error out":                func(context.Context, event.Event) error { return nil },
-		"ctx, EventResponse in, error out":        func(context.Context, *event.EventResponse) error { return nil },
-		"ctx, Event, EventResponse in, error out": func(context.Context, event.Event, *event.EventResponse) error { return nil },
-		"Event in, no out":                        func(event.Event) {},
-		"EventResponse in, no out":                func(*event.EventResponse) {},
-		"Event, EventResponse in, no out":         func(event.Event, *event.EventResponse) {},
-		"Event in, error out":                     func(event.Event) error { return nil },
-		"EventResponse in, error out":             func(*event.EventResponse) error { return nil },
-		"Event, EventResponse in, error out":      func(event.Event, *event.EventResponse) error { return nil },
+		"no in, no out": func() {},
+
+		"ctx in, no out":       func(context.Context) {},
+		"Event in, no out":     func(event.Event) {},
+		"ctx+Event in, no out": func(context.Context, event.Event) {},
+
+		"no in, error out":       func() error { return nil },
+		"no in, Result out":      func() transport.Result { return nil },
+		"no in, Event+error out": func() (*event.Event, error) { return nil, nil },
+
+		"ctx in, error out":       func(context.Context) error { return nil },
+		"Event in, error out":     func(event.Event) error { return nil },
+		"ctx+Event in, error out": func(context.Context, event.Event) error { return nil },
+
+		"ctx in, Event out":       func(context.Context) *event.Event { return nil },
+		"Event in, Event out":     func(event.Event) *event.Event { return nil },
+		"ctx+Event in, Event out": func(context.Context, event.Event) *event.Event { return nil },
+
+		"ctx in, Event+error out":       func(context.Context) (*event.Event, error) { return nil, nil },
+		"Event in, Event+error out":     func(event.Event) (*event.Event, error) { return nil, nil },
+		"ctx+Event in, Event+error out": func(context.Context, event.Event) (*event.Event, error) { return nil, nil },
+
+		"ctx in, Event+Result out":       func(context.Context) (*event.Event, transport.Result) { return nil, nil },
+		"Event in, Event+Result out":     func(event.Event) (*event.Event, transport.Result) { return nil, nil },
+		"ctx+Event in, Event+Result out": func(context.Context, event.Event) (*event.Event, transport.Result) { return nil, nil },
 	} {
 		t.Run(name, func(t *testing.T) {
 			if _, err := receiver(fn); err != nil {
@@ -39,22 +49,22 @@ func TestReceiverFnValidTypes(t *testing.T) {
 
 func TestReceiverFnInvalidTypes(t *testing.T) {
 	for name, fn := range map[string]interface{}{
-		"wrong type in":                func(string) {},
-		"wrong type out":               func() string { return "" },
-		"extra in":                     func(context.Context, event.Event, *event.EventResponse, map[string]string) {},
-		"extra out":                    func(context.Context, *event.EventResponse) (error, int) { return nil, 0 },
-		"context dup EventResponse in": func(context.Context, *event.EventResponse, *event.EventResponse) {},
-		"dup EventResponse in":         func(*event.EventResponse, *event.EventResponse) {},
-		"context dup Event in":         func(context.Context, event.Event, event.Event) {},
-		"dup Event in":                 func(event.Event, event.Event) {},
-		"wrong order, context3 in":     func(*event.EventResponse, *event.EventResponse, context.Context) {},
-		"wrong order, event in":        func(context.Context, *event.EventResponse, event.Event) {},
-		"wrong order, resp in":         func(*event.EventResponse, event.Event) {},
-		"wrong order, context2 in":     func(*event.EventResponse, context.Context) {},
-		"Event as ptr in":              func(*event.Event) {},
-		"EventResponse as non-ptr in":  func(event.EventResponse) {},
-		"extra Event in":               func(event.Event, *event.EventResponse, event.Event) {},
-		"not a function":               map[string]string(nil),
+		"wrong type in":            func(string) {},
+		"wrong type out":           func() string { return "" },
+		"extra in":                 func(context.Context, event.Event, map[string]string) {},
+		"extra out":                func(context.Context) (error, int) { return nil, 0 },
+		"dup error out":            func(context.Context) (transport.Result, error) { return nil, nil },
+		"context dup Event out":    func(context.Context) (*event.Event, *event.Event) { return nil, nil },
+		"context dup Event in":     func(context.Context, event.Event, event.Event) {},
+		"dup Event in":             func(event.Event, event.Event) {},
+		"wrong order, context3 in": func(*event.Event, event.Event, context.Context) {},
+		"wrong order, event in":    func(context.Context, *event.Event, event.Event) {},
+		"wrong order, resp in":     func(*event.Event, event.Event) {},
+		"wrong order, context2 in": func(*event.Event, context.Context) {},
+		"Event as ptr in":          func(*event.Event) {},
+		"Event as non-ptr out":     func() event.Event { return event.Event{} },
+		"extra Event in":           func(event.Event, event.Event) {},
+		"not a function":           map[string]string(nil),
 	} {
 		t.Run(name, func(t *testing.T) {
 			if _, err := receiver(fn); err == nil {
@@ -65,17 +75,17 @@ func TestReceiverFnInvalidTypes(t *testing.T) {
 }
 
 func TestReceiverFnInvoke_1(t *testing.T) {
-	wantErr := errors.New("UNIT TEST")
 	key := struct{}{}
 	wantCtx := context.WithValue(context.TODO(), key, "UNIT TEST")
 	wantEvent := event.Event{
-		Context: &event.EventContextV1{
-			ID: "UNIT TEST",
-		},
+		Context: &event.EventContextV1{ID: "UNIT TEST"},
+	}
+	wantResp := &event.Event{
+		Context: &event.EventContextV1{ID: "UNIT TEST"},
 	}
-	wantResp := &event.EventResponse{Reason: "UNIT TEST"}
+	wantResult := errors.New("UNIT TEST")
 
-	fn, err := receiver(func(ctx context.Context, event event.Event, resp *event.EventResponse) error {
+	fn, err := receiver(func(ctx context.Context, event event.Event) (*event.Event, transport.Result) {
 		if diff := cmp.Diff(wantCtx.Value(key), ctx.Value(key)); diff != "" {
 			t.Errorf("unexpected context (-want, +got) = %v", diff)
 		}
@@ -84,24 +94,24 @@ func TestReceiverFnInvoke_1(t *testing.T) {
 			t.Errorf("unexpected event (-want, +got) = %v", diff)
 		}
 
-		if diff := cmp.Diff(wantResp, resp); diff != "" {
-			t.Errorf("unexpected response (-want, +got) = %v", diff)
-		}
-		return wantErr
+		return wantResp, wantResult
 	})
 	if err != nil {
 		t.Errorf("unexpected error, wanted nil got = %v", err)
 	}
 
-	err = fn.invoke(wantCtx, wantEvent, wantResp)
+	resp, result := fn.invoke(wantCtx, wantEvent)
+
+	if diff := cmp.Diff(wantResp, resp); diff != "" {
+		t.Errorf("unexpected response (-want, +got) = %v", diff)
+	}
 
-	if diff := cmp.Diff(wantErr.Error(), err.Error()); diff != "" {
+	if diff := cmp.Diff(wantResult.Error(), result.Error()); diff != "" {
 		t.Errorf("unexpected error (-want, +got) = %v", diff)
 	}
 }
 
 func TestReceiverFnInvoke_2(t *testing.T) {
-	wantErr := errors.New("UNIT TEST")
 	key := struct{}{}
 	ctx := context.WithValue(context.TODO(), key, "UNIT TEST")
 	wantEvent := event.Event{
@@ -109,25 +119,28 @@ func TestReceiverFnInvoke_2(t *testing.T) {
 			ID: "UNIT TEST",
 		},
 	}
-	wantResp := &event.EventResponse{Reason: "UNIT TEST"}
+	wantResp := &event.Event{
+		Context: &event.EventContextV1{ID: "UNIT TEST"},
+	}
+	wantResult := errors.New("UNIT TEST")
 
-	fn, err := receiver(func(event event.Event, resp *event.EventResponse) error {
+	fn, err := receiver(func(event event.Event) (*event.Event, transport.Result) {
 		if diff := cmp.Diff(wantEvent, event); diff != "" {
 			t.Errorf("unexpected event (-want, +got) = %v", diff)
 		}
-
-		if diff := cmp.Diff(wantResp, resp); diff != "" {
-			t.Errorf("unexpected response (-want, +got) = %v", diff)
-		}
-		return wantErr
+		return wantResp, wantResult
 	})
 	if err != nil {
 		t.Errorf("unexpected error, wanted nil got = %v", err)
 	}
 
-	err = fn.invoke(ctx, wantEvent, wantResp)
+	resp, result := fn.invoke(ctx, wantEvent)
 
-	if diff := cmp.Diff(wantErr.Error(), err.Error()); diff != "" {
+	if diff := cmp.Diff(wantResp, resp); diff != "" {
+		t.Errorf("unexpected response (-want, +got) = %v", diff)
+	}
+
+	if diff := cmp.Diff(wantResult.Error(), result.Error()); diff != "" {
 		t.Errorf("unexpected error (-want, +got) = %v", diff)
 	}
 }
@@ -140,99 +153,79 @@ func TestReceiverFnInvoke_3(t *testing.T) {
 			ID: "UNIT TEST",
 		},
 	}
-	wantResp := &event.EventResponse{Reason: "UNIT TEST"}
+	wantResp := &event.Event{
+		Context: &event.EventContextV1{ID: "UNIT TEST"},
+	}
 
-	fn, err := receiver(func(event event.Event, resp *event.EventResponse) {
-		if diff := cmp.Diff(wantEvent, event); diff != "" {
+	fn, err := receiver(func(e event.Event) *event.Event {
+		if diff := cmp.Diff(wantEvent, e); diff != "" {
 			t.Errorf("unexpected event (-want, +got) = %v", diff)
 		}
 
-		if diff := cmp.Diff(wantResp, resp); diff != "" {
-			t.Errorf("unexpected response (-want, +got) = %v", diff)
-		}
+		return wantResp
 	})
 	if err != nil {
 		t.Errorf("unexpected error, wanted nil got = %v", err)
 	}
 
-	err = fn.invoke(ctx, wantEvent, wantResp)
+	resp, result := fn.invoke(ctx, wantEvent)
 
-	if err != nil {
-		t.Errorf("unexpected error, want nil got got = %v", err.Error())
+	if diff := cmp.Diff(wantResp, resp); diff != "" {
+		t.Errorf("unexpected response (-want, +got) = %v", diff)
+	}
+
+	if result != nil {
+		t.Errorf("unexpected error (-want, +got) = %v", result)
 	}
 }
 
 func TestReceiverFnInvoke_4(t *testing.T) {
-	wantErr := errors.New("UNIT TEST")
 	key := struct{}{}
 	ctx := context.WithValue(context.TODO(), key, "UNIT TEST")
-	e := event.Event{
-		Context: &event.EventContextV1{
-			ID: "UNIT TEST",
-		},
+	wantResp := &event.Event{
+		Context: &event.EventContextV1{ID: "UNIT TEST"},
 	}
-	wantResp := &event.EventResponse{Reason: "UNIT TEST"}
+	wantResult := errors.New("UNIT TEST")
 
-	fn, err := receiver(func(resp *event.EventResponse) error {
-		if diff := cmp.Diff(wantResp, resp); diff != "" {
-			t.Errorf("unexpected response (-want, +got) = %v", diff)
-		}
-		return wantErr
+	fn, err := receiver(func() (*event.Event, transport.Result) {
+		return wantResp, wantResult
 	})
 	if err != nil {
 		t.Errorf("unexpected error, wanted nil got = %v", err)
 	}
 
-	err = fn.invoke(ctx, e, wantResp)
+	resp, result := fn.invoke(ctx, event.Event{})
+
+	if diff := cmp.Diff(wantResp, resp); diff != "" {
+		t.Errorf("unexpected response (-want, +got) = %v", diff)
+	}
 
-	if diff := cmp.Diff(wantErr.Error(), err.Error()); diff != "" {
+	if diff := cmp.Diff(wantResult.Error(), result.Error()); diff != "" {
 		t.Errorf("unexpected error (-want, +got) = %v", diff)
 	}
 }
 
 func TestReceiverFnInvoke_5(t *testing.T) {
-	wantErr := errors.New("UNIT TEST")
 	key := struct{}{}
 	ctx := context.WithValue(context.TODO(), key, "UNIT TEST")
-	e := event.Event{
-		Context: &event.EventContextV1{
-			ID: "UNIT TEST",
-		},
-	}
-	resp := &event.EventResponse{Reason: "UNIT TEST"}
 
-	fn, err := receiver(func() error {
-		return wantErr
+	var wantResp *event.Event
+	wantResult := errors.New("UNIT TEST")
+
+	fn, err := receiver(func() transport.Result {
+		return wantResult
 	})
 	if err != nil {
 		t.Errorf("unexpected error, wanted nil got = %v", err)
 	}
 
-	err = fn.invoke(ctx, e, resp)
+	resp, result := fn.invoke(ctx, event.Event{})
 
-	if diff := cmp.Diff(wantErr.Error(), err.Error()); diff != "" {
-		t.Errorf("unexpected error (-want, +got) = %v", diff)
+	if diff := cmp.Diff(wantResp, resp); diff != "" {
+		t.Errorf("unexpected response (-want, +got) = %v", diff)
 	}
-}
 
-func TestReceiverFnInvoke_6(t *testing.T) {
-	key := struct{}{}
-	ctx := context.WithValue(context.TODO(), key, "UNIT TEST")
-	e := event.Event{
-		Context: &event.EventContextV1{
-			ID: "UNIT TEST",
-		},
-	}
-	resp := &event.EventResponse{Reason: "UNIT TEST"}
-
-	fn, err := receiver(func() {})
-	if err != nil {
-		t.Errorf("unexpected error, wanted nil got = %v", err)
-	}
-
-	err = fn.invoke(ctx, e, resp)
-
-	if err != nil {
-		t.Errorf("unexpected error, want nil got got = %v", err.Error())
+	if diff := cmp.Diff(wantResult.Error(), result.Error()); diff != "" {
+		t.Errorf("unexpected error (-want, +got) = %v", diff)
 	}
 }
diff --git a/pkg/event/event_response_test.go b/pkg/event/event_response_test.go
deleted file mode 100644
index ad578ec..0000000
--- a/pkg/event/event_response_test.go
+++ /dev/null
@@ -1,71 +0,0 @@
-package event
-
-import (
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-)
-
-func TestEventResponse_RespondWith(t *testing.T) {
-	testCases := map[string]struct {
-		t      *EventResponse
-		e      *Event
-		status int
-		want   *EventResponse
-	}{
-		"nil": {},
-		"valid": {
-			t:      &EventResponse{},
-			e:      &Event{Data: "unit test"},
-			status: 200,
-			want: &EventResponse{
-				Status: 200,
-				Event:  &Event{Data: "unit test"},
-			},
-		},
-	}
-	for n, tc := range testCases {
-		t.Run(n, func(t *testing.T) {
-
-			tc.t.RespondWith(tc.status, tc.e)
-
-			got := tc.t
-
-			if diff := cmp.Diff(tc.want, got); diff != "" {
-				t.Errorf("unexpected  (-want, +got) = %v", diff)
-			}
-		})
-	}
-}
-
-func TestEventResponse_Error(t *testing.T) {
-	testCases := map[string]struct {
-		t      *EventResponse
-		msg    string
-		status int
-		want   *EventResponse
-	}{
-		"nil": {},
-		"valid": {
-			t:      &EventResponse{},
-			msg:    "unit test",
-			status: 400,
-			want: &EventResponse{
-				Status: 400,
-				Reason: "unit test",
-			},
-		},
-	}
-	for n, tc := range testCases {
-		t.Run(n, func(t *testing.T) {
-
-			tc.t.Error(tc.status, tc.msg)
-
-			got := tc.t
-
-			if diff := cmp.Diff(tc.want, got); diff != "" {
-				t.Errorf("unexpected  (-want, +got) = %v", diff)
-			}
-		})
-	}
-}
diff --git a/pkg/transport/amqp/transport_test.go b/pkg/transport/amqp/transport_test.go
index 4dd03ac..2808d60 100644
--- a/pkg/transport/amqp/transport_test.go
+++ b/pkg/transport/amqp/transport_test.go
@@ -44,9 +44,9 @@ type tester struct {
 	got  chan interface{} // ce.Event or error
 }
 
-func (t *tester) Delivery(_ context.Context, e event.Event, _ *event.EventResponse) error {
+func (t *tester) Delivery(_ context.Context, e event.Event) (*event.Event, transport.Result) {
 	t.got <- e
-	return nil
+	return nil, nil
 }
 
 func (t *tester) Close() {
diff --git a/test/benchmark/e2e/http/main.go b/test/benchmark/e2e/http/main.go
index 867b5bf..9ecf7f4 100644
--- a/test/benchmark/e2e/http/main.go
+++ b/test/benchmark/e2e/http/main.go
@@ -160,12 +160,12 @@ type benchDelivery struct {
 	fn transport.DeliveryFunc
 }
 
-func (b *benchDelivery) Delivery(ctx context.Context, e event.Event, er *event.EventResponse) error {
-	return b.fn(ctx, e, er)
+func (b *benchDelivery) Delivery(ctx context.Context, e event.Event) (*event.Event, transport.Result) {
+	return b.fn(ctx, e)
 }
 
 func dispatchReceiver(clients []cloudevents.Client, outputSenders int) transport.Delivery {
-	return &benchDelivery{fn: func(ctx context.Context, e cloudevents.Event, er *cloudevents.EventResponse) error {
+	return &benchDelivery{fn: func(ctx context.Context, e cloudevents.Event) (*cloudevents.Event, error) {
 		var wg sync.WaitGroup
 		for i := 0; i < outputSenders; i++ {
 			wg.Add(1)
@@ -175,8 +175,7 @@ func dispatchReceiver(clients []cloudevents.Client, outputSenders int) transport
 			}(clients[i])
 		}
 		wg.Wait()
-		er.RespondWith(200, nil)
-		return nil
+		return nil, nil
 	}}
 }
 
diff --git a/test/integration/http/loopback.go b/test/integration/http/loopback.go
index cdc6177..e2c12f7 100644
--- a/test/integration/http/loopback.go
+++ b/test/integration/http/loopback.go
@@ -89,10 +89,8 @@ func ClientLoopback(t *testing.T, tc TapTest, opts ...interface{}) {
 	recvCtx, recvCancel := context.WithCancel(context.Background())
 
 	go func() {
-		if err := ce.StartReceiver(recvCtx, func(resp *cloudevents.EventResponse) {
-			if tc.resp != nil {
-				resp.RespondWith(200, tc.resp)
-			}
+		if err := ce.StartReceiver(recvCtx, func() *cloudevents.Event {
+			return tc.resp
 		}); err != nil {
 			t.Log(err)
 		}
diff --git a/test/integration/http/middleware.go b/test/integration/http/middleware.go
index 1375cb4..4744e3b 100644
--- a/test/integration/http/middleware.go
+++ b/test/integration/http/middleware.go
@@ -60,8 +60,8 @@ func ClientMiddleware(t *testing.T, tc TapTest, opts ...interface{}) {
 	recvCtx, recvCancel := context.WithCancel(context.Background())
 
 	go func() {
-		if err := ce.StartReceiver(recvCtx, func(event cloudevents.Event, resp *cloudevents.EventResponse) {
-			resp.RespondWith(200, &event)
+		if err := ce.StartReceiver(recvCtx, func(event cloudevents.Event) *cloudevents.Event {
+			return &event
 		}); err != nil {
 			t.Log(err)
 		}