Skip to main content

How do I set up interactive Slack approval buttons for invoice approval?

Post the invoice as a message with an interactive button block, then respond to the click by opening a modal with the trigger_id Slack sends in the interaction payload — within three seconds, before any lookup or validation. Loopfour's Slack integration supports this through sendMessage (with blocks) and openModal, but the modal call has to happen first, not last.

Zuny FesterBy Zuny Fester, Head of Operations and Marketing
Reviewed by Zuny Fester
Published Last reviewed Editorial policy

Part of the finance integrations guide.

What posts the buttonsendMessage (chat.postMessage) with a blocks array containing a button element
What Slack sends back on clickAn interaction payload including a trigger_id, valid for three seconds and single-use
What opens the approval formopenModal (views.open), called with that trigger_id
What populates it afterwardupdateModal (views.update) — not trigger_id-gated, safe to call after slower checks
Common mistakeRunning a permissions or invoice-status check before calling openModal, which burns the three-second window

Posting the approval button

Loopfour's Slack integration posts the invoice message via sendMessage, which maps to Slack's chat.postMessage. To make the message interactive, the request includes a blocks array — Slack's own documentation describes blocks as "a JSON-based array of structured blocks, presented as a URL-encoded string," and a button is one of the interactive elements a block can contain. The message's plain text field still matters even with blocks present: Slack treats it as the fallback shown in notifications and previews.

What happens the moment someone clicks it

Clicking the button doesn't call anything in your workflow directly — Slack sends an interaction payload to your app. Slack's own docs describe this as the mechanism by which "certain events and interactions occur between users and your app, you'll receive a trigger_id as part of the interaction payload." That trigger_id is what openModal needs to actually show a form. It's short-lived by design: three seconds, one use (the sibling diagnostic on this cluster covers what happens when that window is missed).

The order that actually works

Call openModal immediately, with a minimal or loading-state view, before doing anything else. Once the modal is open, use updateModal to fill it with the real approval form — the invoice amount, vendor, and an approve/reject choice — after any permissions check or data lookup finishes. updateModal identifies the view by viewId or externalId rather than trigger_id, so it isn't subject to the three-second limit. This is the only reliable order: open first, populate second.

Handling the approve or reject submission

When the approver submits the modal, Slack sends another interaction payload — this one with the form's values and a fresh trigger_id if you need to open yet another modal (a confirmation, for instance). The original invoice message can then be updated via updateMessage to show the outcome, so the approval trail stays visible in the channel rather than only inside a modal no one else saw.

Next step

Map the finance workflow with the most exposure and prove the automation path.

Bring the invoice, contract, payment reconciliation, or customer finance workflow you have to defend at audit. Loopfour can map the trigger, controls, integrations, and approval loop.

Book a workflow review

Checklist

Invoice approval button: setup order, not just setup steps

  1. Post the invoice with sendMessage, including a blocks array with an approve/reject button
  2. On the interaction payload, call openModal with the trigger_id as the very first action — no lookups first
  3. Run the permissions check and invoice-status lookup after the modal is already open
  4. Call updateModal with the real form (or an access-denied view) using viewId, not trigger_id
  5. On submission, call updateMessage on the original post so the outcome is visible in-channel

Frequently Asked Questions

Yes, in practice — the trigger_id expires in three seconds, and a real permissions check often takes longer than that under load. Open a placeholder modal first, then use updateModal to show either the approval form or an access-denied message once the check finishes.

Not for an interactive button — Slack's interactive elements (buttons, select menus) are only available inside the blocks array. A plain-text message has no click target for Slack to attach a trigger_id to.

Nothing times out automatically on Slack's side beyond the view itself becoming stale to interact with over time — the workflow needs its own logic (a reminder, an escalation after N hours) if it needs to track non-response as its own state.

Sources

Related