Encore logo

Skill

encore-go-webhook

receive webhooks in Encore Go

Published by Encore Updated May 16
Covers Backend Integrations Encore Webhooks Go

Description

Receive inbound webhooks from external services (Stripe, GitHub, Slack, Twilio, etc.) in Encore Go using `//encore:api raw`. The right skill any time the user names a third-party provider that POSTs events to a URL you own.

SKILL.md

Encore Go Webhook Endpoints

Instructions

Use //encore:api raw to receive inbound webhooks from third-party services. Raw endpoints give you direct access to http.ResponseWriter and *http.Request, which you need for signature verification (the verification typically requires the unparsed raw body).

1. Define the endpoint with //encore:api raw

package webhooks

import (
    "io"
    "net/http"
)

//encore:api public raw path=/webhooks/stripe method=POST
func StripeWebhook(w http.ResponseWriter, req *http.Request) {
    sig := req.Header.Get("Stripe-Signature")

    // Read the raw body — needed for signature verification.
    body, err := io.ReadAll(req.Body)
    if err != nil {
        http.Error(w, "could not read body", http.StatusBadRequest)
        return
    }

    // Verify signature, parse event, handle, then respond...

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(`{"received":true}`))
}

2. Verify the signature

Most providers sign webhooks. Read the secret with secrets (see the encore-go-secret skill) and verify before trusting the payload.

package webhooks

import (
    "github.com/stripe/stripe-go/v76/webhook"
)

var secrets struct {
    StripeWebhookSecret string
}

//encore:api public raw path=/webhooks/stripe method=POST
func StripeWebhook(w http.ResponseWriter, req *http.Request) {
    body, _ := io.ReadAll(req.Body)
    sig := req.Header.Get("Stripe-Signature")

    event, err := webhook.ConstructEvent(body, sig, secrets.StripeWebhookSecret)
    if err != nil {
        http.Error(w, "signature verification failed", http.StatusBadRequest)
        return
    }

    // event is now trusted — handle it.
    _ = event
    w.WriteHeader(http.StatusOK)
}

For GitHub, verify the HMAC-SHA256 in the X-Hub-Signature-256 header against the raw body using your webhook secret.

Common providers

ProviderSignature headerVerification
StripeStripe-Signaturestripe.webhook.ConstructEvent(rawBody, sig, secret)
GitHubX-Hub-Signature-256HMAC-SHA256 over the raw body
SlackX-Slack-SignatureHMAC-SHA256 over v0:{timestamp}:{rawBody}
ShopifyX-Shopify-Hmac-Sha256HMAC-SHA256 (base64) over the raw body
TwilioX-Twilio-SignatureHMAC-SHA1 over URL + sorted form fields

Always respond quickly

Webhook senders retry on non-2xx or slow responses. Acknowledge with a 2xx as soon as the payload is verified, then enqueue the actual work via Pub/Sub (see encore-go-pubsub) instead of doing it in the request handler.

package webhooks

import (
    "encore.dev/pubsub"
    "net/http"
)

type StripeEvent struct {
    ID   string `json:"id"`
    Type string `json:"type"`
    Data any    `json:"data"`
}

var StripeEvents = pubsub.NewTopic[*StripeEvent]("stripe-events", pubsub.TopicConfig{
    DeliveryGuarantee: pubsub.AtLeastOnce,
})

//encore:api public raw path=/webhooks/stripe method=POST
func StripeWebhook(w http.ResponseWriter, req *http.Request) {
    // ... verify signature, parse event ...

    _, _ = StripeEvents.Publish(req.Context(), &StripeEvent{
        ID: event.ID, Type: string(event.Type), Data: event.Data,
    })
    w.WriteHeader(http.StatusOK)
}

Guidelines

  • Use //encore:api raw only for webhooks and other low-level HTTP integrations.
  • Always verify the provider's signature before trusting the payload.
  • Always respond 2xx fast — push slow work onto Pub/Sub.
  • Store the signing secret in secrets struct{...}; never inline it.
  • For typed JSON endpoints in your own service, use plain //encore:api from the encore-go-api skill.

© 2026 YourAI.tools. Every skill from an identity-verified publisher.

Independent catalog. Not affiliated with, endorsed by, or sponsored by Anthropic or any listed publisher. All trademarks belong to their respective owners.