Portal Community

LINE Message Types for HIL

TypeDescriptionBest for HIL
Confirm TemplateSimple two-button message with a text bodyApprove/Reject flows — minimal setup
Buttons TemplateText + up to 4 action buttonsMulti-option approvals
Flex MessageFully custom card layout (similar to Adaptive Cards)Rich field display with context + actions
Quick ReplyTap buttons shown above the keyboardQuick approve/reject after a text message

Correlation Strategy: Postback Data

LINE Postback action data field supports up to 300 characters. A prefixed GUID (38 chars) fits easily:

"data": "hil_approve|3fa85f64-5717-4562-b3fc-2c963f66afa6"
"data": "hil_reject|3fa85f64-5717-4562-b3fc-2c963f66afa6"

Building a Confirm Template Message

The Confirm Template is the simplest option for binary approve/reject flows:

private object BuildLineConfirmMessage(
    IReadOnlyList<ResolvedHilField> fields,
    string resId,
    string recipientUserId)
{
    var lines = new List<string> { "Approval Required:" };
    foreach (var field in fields)
    {
        if (field.DisplayMode == HilDisplayMode.Concealed) continue;
        string val = field.DisplayMode == HilDisplayMode.ReadableMasked
            ? "****"
            : field.CurrentValue?.ToString() ?? "—";
        lines.Add($"• {field.Label}: {val}");
    }

    return new {
        to       = recipientUserId,
        messages = new[] {
            new {
                type = "template",
                altText = "Approval required — please open LINE to respond.",
                template = new {
                    type = "confirm",
                    text = string.Join("\n", lines),
                    actions = new object[] {
                        new {
                            type  = "postback",
                            label = "Approve ✅",
                            data  = $"hil_approve|{resId}"
                        },
                        new {
                            type  = "postback",
                            label = "Reject ❌",
                            data  = $"hil_reject|{resId}"
                        }
                    }
                }
            }
        }
    };
}

Building a Flex Message (Rich Layout)

For workflows with many fields, a Flex Message provides a structured card view:

private object BuildLineFlexMessage(
    IReadOnlyList<ResolvedHilField> fields,
    string resId,
    string recipientUserId)
{
    var bodyContents = new List<object>();
    bodyContents.Add(new {
        type   = "text",
        text   = "Approval Required",
        weight = "bold",
        size   = "xl",
        color  = "#6c8cff"
    });
    bodyContents.Add(new { type = "separator", margin = "md" });

    foreach (var field in fields)
    {
        if (field.DisplayMode == HilDisplayMode.Concealed) continue;
        string val = field.DisplayMode == HilDisplayMode.ReadableMasked
            ? "••••••••"
            : field.CurrentValue?.ToString() ?? "—";

        bodyContents.Add(new {
            type     = "box",
            layout   = "horizontal",
            margin   = "sm",
            contents = new object[] {
                new { type = "text", text = field.Label, color = "#8892b0", size = "sm", flex = 2 },
                new { type = "text", text = val, color = "#e2e8f0", size = "sm", flex = 3, wrap = true }
            }
        });
    }

    return new {
        to       = recipientUserId,
        messages = new[] {
            new {
                type    = "flex",
                altText = "Approval required",
                contents = new {
                    type   = "bubble",
                    body   = new { type = "box", layout = "vertical", contents = bodyContents },
                    footer = new {
                        type     = "box",
                        layout   = "horizontal",
                        contents = new object[] {
                            new {
                                type   = "button",
                                style  = "primary",
                                color  = "#34d399",
                                action = new { type = "postback", label = "Approve", data = $"hil_approve|{resId}" }
                            },
                            new {
                                type   = "button",
                                style  = "primary",
                                color  = "#f87171",
                                action = new { type = "postback", label = "Reject", data = $"hil_reject|{resId}" }
                            }
                        }
                    }
                }
            }
        }
    };
}

Sending the Message

// POST to https://api.line.me/v2/bot/message/push
await _httpClient.PostAsJsonAsync(
    "https://api.line.me/v2/bot/message/push",
    message,
    cancellationToken);
// Set Authorization: Bearer {channelAccessToken} header

return NodeExecutionResult.Suspend("waiting");

Webhook Handler

LINE sends a JSON webhook with an events array. Postback interactions arrive with type = "postback" and postback.data containing the value you set.

[HttpPost("webhooks/line")]
public async Task<IActionResult> Handle([FromBody] LineWebhookPayload payload)
{
    // 1. Validate LINE signature (HMAC-SHA256 over raw body with channel secret)
    var rawBody   = await Request.ReadBodyAsStringAsync();
    var signature = Request.Headers["X-Line-Signature"].ToString();
    if (!_lineSignatureVerifier.Verify(rawBody, signature))
        return Unauthorized();

    foreach (var ev in payload.Events)
    {
        if (ev.Type != "postback") continue;

        var data   = ev.Postback?.Data;
        if (data == null || !data.StartsWith("hil_")) continue;

        var parts  = data.Split('|', 2);
        var action = parts[0]; // "hil_approve" or "hil_reject"
        var resId  = parts[1];

        var responseData = new Dictionary<string, object>
        {
            ["approved"]    = action == "hil_approve",
            ["respondedBy"] = ev.Source?.UserId
        };

        _ = Task.Run(() => _continuationOrchestrator.ContinueAsync(resId, responseData));
    }

    return Ok();
}

LINE Signature Verification

// Compute HMAC-SHA256(channelSecret, rawBody) → Base64
var key  = Encoding.UTF8.GetBytes(channelSecret);
var body = Encoding.UTF8.GetBytes(rawBody);
using var hmac = new HMACSHA256(key);
var computed   = Convert.ToBase64String(hmac.ComputeHash(body));
bool valid     = computed == signatureHeader;

Required Configuration

Config FieldValue
ChannelAccessToken@{secret:LineChannelAccessToken}
ChannelSecret@{secret:LineChannelSecret}
RecipientUserId@{input:lineUserId}
UseFlexMessagetrue / false — use Confirm Template for simple flows
Getting the LINE User ID LINE User IDs (beginning with U) are obtained from inbound webhook events when a user adds your bot as a friend. Store the User ID against your user records at that point, then reference it via @{input:lineUserId} in the workflow.
altText is required LINE displays the altText field as a push notification preview and in chat list summaries. Always set a meaningful altText — "Approval required" ensures the human knows something needs their attention even before opening the chat.