diff --git a/repositories/events/events.go b/repositories/events/events.go
index 560f01319da127ad7919545738d962b3495f840a..7ed5c474bfbdf14d06a07e4eecf3e5ca161d27fd 100644
--- a/repositories/events/events.go
+++ b/repositories/events/events.go
@@ -1,5 +1,10 @@
 package events
 
+import (
+	"bytes"
+	"encoding/json"
+)
+
 const (
 	PushEvent string = "push"
 )
@@ -16,3 +21,70 @@ func IsValidEvent(event string) bool {
 	_, ok := validEvents[event]
 	return ok
 }
+
+// The payload type.
+type Payload interface {
+	// The event the payload is for.
+	//
+	// This will be included in the marshalled payload.
+	GetEvent() string
+
+	// The repository name this payload is for.
+	//
+	// This will be included in the marshalled payload.
+	GetRepository() string
+
+	// Extra content to be included in the payload.
+	//
+	// This returns a tuple of `(fieldName, fieldContents)`.
+	//
+	// The contents must be `json.Marshal`-able.
+	GetContent() (string, interface{})
+}
+
+// Marshal a payload into a JSON blob.
+func MarshalPayload(p Payload) ([]byte, error) {
+	buffer := bytes.NewBufferString("{\n")
+
+	b, err := json.Marshal(p.GetEvent())
+	if err != nil {
+		return nil, err
+	}
+
+	buffer.WriteString("\t\"event\": ")
+	buffer.Write(b)
+	buffer.WriteString(",\n")
+
+	b, err = json.Marshal(p.GetRepository())
+	if err != nil {
+		return nil, err
+	}
+
+	buffer.WriteString("\t\"repository\": ")
+	buffer.Write(b)
+
+	fieldName, content := p.GetContent()
+	if fieldName != "" {
+		buffer.WriteString(",\n")
+
+		b, err = json.Marshal(fieldName)
+		if err != nil {
+			return nil, err
+		}
+
+		buffer.WriteString("\t")
+		buffer.Write(b)
+		buffer.WriteString(": ")
+
+		b, err = json.MarshalIndent(content, "\t", "\t")
+		if err != nil {
+			return nil, err
+		}
+
+		buffer.Write(b)
+	}
+
+	buffer.WriteString("\n}\n")
+
+	return buffer.Bytes(), nil
+}
diff --git a/repositories/events/events_test.go b/repositories/events/events_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..35ca95774d6980828f24896f06b2c032a2c48801
--- /dev/null
+++ b/repositories/events/events_test.go
@@ -0,0 +1,88 @@
+package events_test
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/reviewboard/rb-gateway/repositories/events"
+)
+
+func TestMarshalPushPayload(t *testing.T) {
+	assert := assert.New(t)
+
+	payload := events.PushPayload{
+		Repository: "foo",
+		Commits: []events.PushPayloadCommit{
+			{
+				Id:      "abababab",
+				Message: "Commit message 1",
+				Target: events.PushPayloadCommitTarget{
+					Branch: "master",
+					Tags:   []string{"v1"},
+				},
+			},
+			{
+				Id:      "cdcdcdcd",
+				Message: "Commit message 2",
+				Target: events.PushPayloadCommitTarget{
+					Branch: "dev",
+				},
+			},
+			{
+				Id:      "efefefef",
+				Message: "Commit message 3",
+				Target: events.PushPayloadCommitTarget{
+					Branch:    "default",
+					Bookmarks: []string{"my-bookmark"},
+					Tags:      []string{"dev", "foo"},
+				},
+			},
+		},
+	}
+
+	bytes, err := events.MarshalPayload(payload)
+	assert.Nil(err)
+	assert.NotNil(bytes)
+
+	expected := `{
+	"event": "push",
+	"repository": "foo",
+	"commits": [
+		{
+			"id": "abababab",
+			"message": "Commit message 1",
+			"target": {
+				"branch": "master",
+				"tags": [
+					"v1"
+				]
+			}
+		},
+		{
+			"id": "cdcdcdcd",
+			"message": "Commit message 2",
+			"target": {
+				"branch": "dev"
+			}
+		},
+		{
+			"id": "efefefef",
+			"message": "Commit message 3",
+			"target": {
+				"branch": "default",
+				"bookmarks": [
+					"my-bookmark"
+				],
+				"tags": [
+					"dev",
+					"foo"
+				]
+			}
+		}
+	]
+}
+`
+
+	assert.Equal(expected, string(bytes))
+}
diff --git a/repositories/events/push.go b/repositories/events/push.go
new file mode 100644
index 0000000000000000000000000000000000000000..854d8cc824322a9daff111d4478c1140d9a398d4
--- /dev/null
+++ b/repositories/events/push.go
@@ -0,0 +1,51 @@
+package events
+
+// A payload for a push event.
+type PushPayload struct {
+	// The repository where the event occurred.
+	Repository string `json:"repository"`
+
+	// The commits that were pushed.
+	Commits []PushPayloadCommit `json:"commits"`
+}
+
+// A commit that is part of the push.
+type PushPayloadCommit struct {
+	// The commit ID.
+	Id string `json:"id"`
+
+	// The commit message.
+	Message string `json:"message"`
+
+	// The targets the commit was pushed to.
+	Target PushPayloadCommitTarget `json:"target"`
+}
+
+// A target for a push.
+type PushPayloadCommitTarget struct {
+	// The branch the commit was pushed to, if any.
+	Branch string `json:"branch,omitempty"`
+
+	// The bookmarks that point at the commit, if any.
+	//
+	// This can only be non-empty for Mercurial.
+	Bookmarks []string `json:"bookmarks,omitempty"`
+
+	// The tags that point at the commit, if any.
+	Tags []string `json:"tags,omitempty"`
+}
+
+// Return the event the payload corresponds to.
+func (_ PushPayload) GetEvent() string {
+	return PushEvent
+}
+
+// Return the repository where the event occurred.
+func (p PushPayload) GetRepository() string {
+	return p.Repository
+}
+
+// Return the contents of the payload.
+func (p PushPayload) GetContent() (string, interface{}) {
+	return "commits", p.Commits
+}
