Menu

Go SDK — Webhooks

WebhooksServiceClient registers HTTPS endpoints for resource events. Deliveries are signed with HMAC-SHA256 (X-Metalhost-Signature).

Event types

Convention: {domain}.{resource}.{verb}

  • compute.vm.created / compute.vm.deleted / compute.vm.state_changed
  • storage.disk.created / storage.disk.deleted
  • ops.operation.completed
  • billing.invoice.finalized

Subscribe to specific types or wildcards: compute.vm.*

Create a subscription

hooks := webhooksv1connect.NewWebhooksServiceClient(httpClient, base)
resp, err := hooks.CreateSubscription(ctx, connect.NewRequest(
    &webhooksv1.CreateSubscriptionRequest{
        Name:        "webhook-subscriptions/deploy-hook",
        ProjectName: "projects/my-app",
        EndpointUrl: "https://hooks.example.com/metalhost",
        EventTypes:  []string{"compute.vm.*", "ops.operation.completed"},
    },
))
secret := resp.Msg.GetSecret() // HMAC signing secret — shown once

endpointUrl must be HTTPS in production. Subscription name: webhook-subscriptions/{slug}.

Manage subscriptions

hooks.ListSubscriptions(ctx, connect.NewRequest(&webhooksv1.ListSubscriptionsRequest{ProjectName: project}))
hooks.UpdateSubscription(ctx, ...) // pause/resume via state
hooks.DeleteSubscription(ctx, connect.NewRequest(&webhooksv1.DeleteSubscriptionRequest{Name: subName}))

Delivery log

hooks.ListDeliveries(ctx, connect.NewRequest(&webhooksv1.ListDeliveriesRequest{
    SubscriptionName: subName,
    PageSize: 50,
}))

Last 100 attempts per subscription — status code, response snippet, retry count.

Verify signatures

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

func verify(payload []byte, signatureHeader, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signatureHeader))
}

Also check X-Metalhost-Subscription header matches your subscription id. Reject replays with event id deduplication on your side.

What's next