Buf Schema Registry (BSR)

Webhooks

ProEnterprise

This information only applies to organizations on the Pro and Enterprise plans.

Private BSR instances offer webhooks to notify your backend services when a specific event of interest happens. This allows you to build integrations triggering further actions—for example:

  • Run a CI/CD pipeline to run appropriate downstream builds, tests, deployments
  • Notify interested parties to inform them of any changes to .proto files or versions
  • Tag your git repository based on your BSR tags

Webhooks are in alpha, which includes support for a single event: a successful buf push on a repository.

Webhooks are disabled by default. Contact Support or your Buf representative if you want webhooks enabled for your private BSR instance. If webhooks are enabled, then by default they will process batches of up to 20 events every 5 seconds.

Integrating consists of writing an event listener and then subscribing to events. The event listener must implement the EventService and subscriptions are managed through the WebhookService. These services can be easily interacted with using the Connect library, as shown below in Webhooks with Connect.

If you can't use Connect, see the Webhooks without Connect guide below that shows you how to:

  • manage webhook subscriptions using a regular curl+JSON payload; and
  • build an event listener that's compatible with Connect (application/proto) requests.

In the samples below, please use the address for your private BSR server (for example, https://buf.example.com) in place of ${PRIVATE_BSR_HOSTNAME}.

Webhooks with Connect

This guide uses a Go Connect client and generated Go code for the webhooks service. This code was generated using the connectrpc/go plugin in the BSR public repository.

The generated module from that plugin includes:

The steps should be very similar for the JS generated code, which uses the protocolbuffers/js plugin.

Prepare to receive webhooks

You build the webhook listener using the Connect client code generated from the Webhook Event service. First, fetch the generated go module from the connect-go plugin:

$ go get buf.build/gen/go/bufbuild/buf/connectrpc/go

Here's an example Connect implementation:

package main

import (
    "context"
    "fmt"
    "net/http"

    connect "connectrpc.com/connect"
    registryv1alpha1 "buf.build/gen/go/bufbuild/buf/connectrpc/go/buf/alpha/registry/v1alpha1"
    webhookv1alpha1 "buf.build/gen/go/bufbuild/buf/connectrpc/go/buf/alpha/webhook/v1alpha1"
    webhookconnect "buf.build/gen/go/bufbuild/buf/connectrpc/go/buf/alpha/webhook/v1alpha1/webhookv1alpha1connect"
)

type webhookEventHandler struct{}

func (h *webhookEventHandler) Event(
    ctx context.Context,
    req *connect.Request[webhookv1alpha1.EventRequest],
) (*connect.Response[webhookv1alpha1.EventResponse], error) {
    // Handle the type-safe incoming request for the push event:
    payload := req.Msg
    switch payload.Event {
    case registryv1alpha1.WebhookEvent_WEBHOOK_EVENT_REPOSITORY_PUSH:
        pushEvent := payload.Payload.GetRepositoryPush()
        fmt.Println("received repo push event:", pushEvent)
    default:
        fmt.Println("unknown event:", payload.Event)
    }

    // Webhook listener has an empty response
    return connect.NewResponse(&webhookv1alpha1.EventResponse{}), nil
}

// Connect handler based on: https://connectrpc.com/docs/go/getting-started#implement-handler
func main() {
    mux := http.NewServeMux()
    mux.Handle(webhookconnect.NewEventServiceHandler(&webhookEventHandler{}))
    http.ListenAndServe("localhost:8080", mux)
}

Manage subscriptions

Webhooks are managed through the BSR API. The easiest way to interact with it is to use the generated Connect client. Users with the Admin role in a repository can manage webhook subscriptions for that repository.

Below is an example of managing webhooks using the BSR Webhook service. First, fetch the generated go module from the connect-go template if you didn't in the previous step:

$ go get buf.build/gen/go/bufbuild/buf/connectrpc/go

Then in your Go code, set up the subscription:

package main

import (
    "context"
    "log"
    "net/http"

    connect "connectrpc.com/connect"
    registryv1alpha1 "buf.build/gen/go/bufbuild/buf/connectrpc/go/buf/alpha/registry/v1alpha1"
    registryconnect "buf.build/gen/go/bufbuild/buf/connectrpc/go/buf/alpha/registry/v1alpha1/registryv1alpha1connect"
)

func main() {
    connectClient := registryconnect.NewWebhookServiceClient(
        http.DefaultClient,
        "https://${PRIVATE_BSR_HOSTNAME}", // BSR API
    )

    // Creating the webhook. Each repository event allows a single webhook. Since
    // we support only push events at the moment, this means that a repository can
    // have just one webhook subscription in total.
    createReq := connect.NewRequest(&registryv1alpha1.CreateWebhookRequest{
        WebhookEvent:   registryv1alpha1.WebhookEvent_WEBHOOK_EVENT_REPOSITORY_PUSH,
        OwnerName:      "ORG_NAME_OR_USERNAME",
        RepositoryName: "REPOSITORY_NAME",
        CallbackUrl:    "https://your.callback.url/buf.alpha.webhook.v1alpha1.EventService/Event",
    })
    createReq.Header().Add("Authorization", "Bearer YOUR_API_TOKEN")

    createResp, err := connectClient.CreateWebhook(context.Background(), createReq)
    if err != nil {
        log.Fatalf("creating webhook failed: %v", err)
    }
    webhook := createResp.Msg.Webhook
    if webhook == nil {
        log.Fatal("nil webhook response")
    }
    log.Println("new webhook created!", webhook)

    // Checking webhook exists
    listReq := connect.NewRequest(&registryv1alpha1.ListWebhooksRequest{
        OwnerName:      "ORG_NAME_OR_USERNAME",
        RepositoryName: "REPOSITORY_NAME",
    })
    listReq.Header().Add("Authorization", "Bearer YOUR_API_TOKEN")

    listResp, err := connectClient.ListWebhooks(context.Background(), listReq)
    if err != nil {
        log.Fatalf("list webhooks failed: %v", err)
    }
    log.Println("existing webhooks:", listResp)

    // Deleting the webhook. Is your callback URL going to change and you need to
    // update your integration? You can remove you webhook and recreate it again.
    // Note that pending notifications for events that were recently triggered may
    // be delivered anyway after deleting the webhook subscription.
    deleteReq := connect.NewRequest(&registryv1alpha1.DeleteWebhookRequest{
        WebhookId: webhook.WebhookId,
    })
    deleteReq.Header().Add("Authorization", "Bearer YOUR_API_TOKEN")

    deleteResp, err := connectClient.DeleteWebhook(context.Background(), deleteReq)
    if err != nil {
        log.Fatalf("delete webhook failed: %v", err)
    }
    log.Println("webhook deleted:", deleteResp)
}}

Webhooks without Connect

Connect isn't required for communicating with the webhooks service—because the server is also written with Connect handlers, you can make the same calls using curl with JSON.

Prepare to receive webhooks

The BSR supports Connect-compatible backend services for webhooks. Events will trigger an HTTP POST to the subscribed callback URL, with a Content-Type: application/proto header and a proto payload with the event details.

A listener service must exist at the callback URL that is compatible with the Event RPC signature in the public bufbuild proto docs. Here are the possible payloads, and the expected response.

Manage subscriptions with the Buf CLI

You can manage your webhooks with the buf beta registry webhook commands. WEBHOOK_EVENT_REPOSITORY_PUSH is the only event type that's currently available.

Create a webhook subscription with the Buf CLI

$ buf beta registry webhook create \
  --owner="<the organization or username that owns the repository>" \
  --repository="<the repository name>" \
  --callback-url="https://your.callback.url/buf.alpha.webhook.v1alpha1.EventService/Event" \
  --event="WEBHOOK_EVENT_REPOSITORY_PUSH" \
  --remote="${PRIVATE_BSR_HOSTNAME}"

List webhook subscriptions with the Buf CLI

$ buf beta registry webhook list \
  --owner="<the organization or username that owns the repository>" \
  --repository="<the repository name>" \
  --remote="${PRIVATE_BSR_HOSTNAME}"

Delete webhook subscriptions with the Buf CLI

$ buf beta registry webhook delete \
  --id="the-webhook-id-that-will-be-deleted" \
  --remote="${PRIVATE_BSR_HOSTNAME}"

Manage subscriptions with curl

Webhooks are managed through the BSR API. Requests can be made to this API using an RPC client like Connect or via curl commands with JSON payloads.

Create webhook subscription with curl

Use the CreateWebhook RPC to create webhooks for a repository:

$ curl --location --request POST 'https://${PRIVATE_BSR_HOSTNAME}/buf.alpha.registry.v1alpha1.WebhookService/CreateWebhook' \
--header 'Authorization: Bearer <BSR api token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "owner_name": "<the organization or username that owns the repository>",
    "repository_name": "<the repository name>",
    "webhook_event": "WEBHOOK_EVENT_REPOSITORY_PUSH",
    "callback_url": "https://your.callback.url/buf.alpha.webhook.v1alpha1.EventService/Event"
}'
Output
HTTP Status: 200 OK Content-Type: application/json { "webhook": { "event": "WEBHOOK_EVENT_REPOSITORY_PUSH", "webhookId": "the-new-webhook-id", "createTime": "2022-07-11T21:15:27.633999Z", "updateTime": "2022-07-11T21:15:27.633999Z", "repository_name": "<the repository name>", "owner_name": "<the organization or username that owns the repository>", "callbackUrl": "https://your.callback.url/buf.alpha.webhook.v1alpha1.EventService/Event" } }

For the callback_url parameter, make sure you use an https scheme, and you suffix the complete RPC path to the Event method, with no queries, fragments, or any additional characters.

List webhook subscriptions with curl

After creating a webhook subscription for a repository, you can confirm its existence using the ListWebhooks RPC:

$ curl --location --request POST 'https://${PRIVATE_BSR_HOSTNAME}/buf.alpha.registry.v1alpha1.WebhookService/ListWebhooks' \
--header 'Authorization: Bearer <your BSR api token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "owner_name": "<the organization or username that owns the repository>",
    "repository_name": "<the repository name>"
}'

This should return a successful response in the form of:

Output
Content-Type: application/json { "webhooks": [ { "event": "WEBHOOK_EVENT_REPOSITORY_PUSH", "webhookId": "the-webhook-id", "createTime": "2022-07-11T21:15:27.633999Z", "updateTime": "2022-07-11T21:15:27.633999Z", "repository_name": "<the repository name>", "owner_name": "<the organization or username that owns the repository>", "callbackUrl": "https://your.callback.url/buf.alpha.webhook.v1alpha1.EventService/Event" } ] }

Delete webhook subscription with curl

Webhooks can be deleted using the DeleteWebhook RPC:

$ curl --location --request POST 'https://${PRIVATE_BSR_HOSTNAME}/buf.alpha.registry.v1alpha1.WebhookService/DeleteWebhook' \
--header 'Authorization: Bearer <your BSR api token>' \
--header 'Content-Type: application/json' \
--data-raw '{
    "webhook_id": "the-webhook-id-that-will-be-deleted"
}'
Output
HTTP Status: 200 OK Content-Type: application/json {}

Test receiving a webhook push event

You can test receiving repository push events with this snippet:

# Testing your webhook service using binary proto.
# You can use `buf beta convert` to take a JSON payload and
# convert it into binary proto format, and then use that
# as a body payload to send via curl.
$ echo '{
  "event": "WEBHOOK_EVENT_REPOSITORY_PUSH",
  "payload": {
    "repositoryPush": {
      "eventTime": "2022-07-11T15:07:30Z",
      "repository": {
        "id": "my-repo-id",
        "name": "my-repo-name",
        "createTime": "2022-07-10T15:07:30Z",
        "updateTime": "2022-07-10T18:07:30Z",
        "userId": "the-user-id",
        "visibility": "VISIBILITY_PUBLIC"
      },
      "repositoryCommit": {
        "author": "the-author-username",
        "commitSequenceId": 10,
        "createTime": "2022-07-11T15:07:30Z",
        "id": "the-commit-id",
        "name": "the-commit-name",
        "digest": "the-commit-digest",
        "tags": [
          {
            "author": "the-tag-author",
            "commitName": "the-commit-hash",
            "id": "the-tag-id",
            "createTime": "2022-07-11T15:07:30Z",
            "name": "the-tag-name"
          }
        ]
      }
    }
  }
}' | \
	buf beta convert buf.build/bufbuild/buf \
		--input=-#format=json \
		--type buf.alpha.webhook.v1alpha1.EventRequest | \
	curl -X POST https://your.callback.url/buf.alpha.webhook.v1alpha1.EventService/Event \
		-H "Content-Type: application/proto" \
		--data-binary @-

Once a webhook subscription is configured, your listener will begin receiving buf push events with a proto payload in the EventRequest format. The payload includes the commit object that triggered the event (including the author), as well as the repository object that this commit was pushed to. For more details on the payload, visit the docs definition.