Receiving Webhooks
WebhookRequestParser bundles two steps into one call:
- verify the
x-line-signatureheader against the raw request body, and - deserialize the body into a strongly-typed
CallbackRequest.
On failure it throws: WebhookSignatureException when the signature is invalid, and WebhookPayloadException when the body cannot be deserialized (both derive from @Line.OpenApi.Messaging.Webhook.WebhookException).
Register the parser
using Line.OpenApi.Messaging.Webhook.DependencyInjection;
services.AddLineWebhook(o => o.ChannelSecret = "CHANNEL_SECRET");
// resolve: sp.GetRequiredService<WebhookRequestParser>()
Webhook receiving performs no outbound HTTP, so this registration needs no
IHttpClientFactory.
Handle a request (ASP.NET Core)
Reading the raw body and extracting the signature header are your responsibility. The signature is computed over the raw bytes, so you must read the body before model binding.
using Line.OpenApi.Messaging.Webhook;
using Line.OpenApi.Messaging.Webhook.Generated.Models;
app.MapPost("/webhook", async (HttpRequest request, WebhookRequestParser parser) =>
{
using var ms = new MemoryStream();
await request.Body.CopyToAsync(ms);
var body = ms.ToArray(); // the exact bytes that were signed
var signature = request.Headers["x-line-signature"];
CallbackRequest callback;
try
{
callback = await parser.ParseAsync(body, signature);
}
catch (WebhookSignatureException) { return Results.Unauthorized(); } // invalid signature
catch (WebhookPayloadException) { return Results.BadRequest(); } // invalid body
// Events are already reconstructed into concrete types by the `type` discriminator
// (unknown types arrive as the base Event). Branch on the caller side:
foreach (var ev in callback.Events!)
{
switch (ev)
{
case MessageEvent m when m.Message is TextMessageContent t:
Console.WriteLine($"text: {t.Text}");
break;
case FollowEvent: /* user added the bot */ break;
case PostbackEvent p: /* p.Postback!.Data */ break;
// unknown events arrive as the base Event type (safe to ignore)
}
}
return Results.Ok();
});
Multi-tenant secrets
When each channel has its own secret, use the static overload and pass the secret per call:
CallbackRequest callback =
await WebhookRequestParser.ParseAsync(channelSecret, body, signature);
Notes
- Body size limits (DoS protection) are out of scope for this helper. Enforce a raw-body
size limit upstream (for example ASP.NET Core's
MaxRequestBodySize). - The parser deserializes without relying on Kiota's global serializer registry, so it works standalone even in an app that never constructed the Messaging client.