There’s a big difference between building features and making them visible.

A user clicks thumbs down on your product. They add a comment. They upload a screenshot.

And then…

Nothing.

It disappears into logs. Or worse, into a database nobody checks.

That’s the problem. Feedback without visibility is dead data.

A few weeks ago, I wanted a better way.

Whenever a user submits feedback from my React SPA, I wanted it to instantly appear inside a Microsoft Teams channel.

Not as plain text. Not as raw JSON. But as a clean, rich Adaptive Card.

With:

  • User details

  • Rating

  • Reason

  • Comments

  • Attachments

  • Timestamp

  • Action buttons

And the best part?

No Microsoft Graph API.
No bot registration.
No OAuth headache.

Just:

React → Webhook → Teams Channel

In this article, I’ll show you exactly how.

What We’re Building

Every time a user submits feedback:

Example:

👍 Helpful or 👎 Not Helpful

We’ll send this directly into Teams:

  • Name

  • Current Page

  • Rating

  • Feedback reason

  • Comments

  • Screenshot link

  • Submission time

As a rich Adaptive Card. This turns Teams into your live product feedback dashboard.

That’s powerful.

Why Webhooks Instead of Microsoft Graph?

Simple. Because Graph is overkill for this.

Webhooks are perfect when:

You only need to post messages
You want quick setup
No user auth required
Lightweight integration
Faster development

Graph API is for full Teams automation. Webhooks are for shipping faster.

Choose wisely.

Step 1: Create a Team in Microsoft Teams

Open Microsoft Teams.

Go to:

Teams → Join or create team

Click: Create Team

Choose: From scratch

Example:

Team Name:

App Notifications

Description:

Live feedback notifications from React SPA

Create it.

Done.

Step 2: Create a Channel

Inside your Team:

Click:

⋯ → Add channel

Example:

Channel name:

user-feedback

Description:

Tracks live customer feedback events

Now your structure looks like:

App Notifications
 ├── General
 ├── user-feedback

This channel will receive all feedback.

Step 3: Create Webhook (New Microsoft Teams Way)

This changed.

Older tutorials tell you:

⋯ → Connectors

That’s outdated.

In 2026:

Use:

⋯ → Workflows

Search:

Webhook

Choose:

Send webhook alerts to a channel

This is the modern replacement.

Then:

  1. Select your Team

  2. Select your Channel

  3. Create workflow

Microsoft will generate:

https://prod-xx.logic.azure.com/...

Copy this.

Important: Treat it like a secret.

Never expose it publicly.

💡 Enjoying this article?
Every week day, I publish practical, production-ready deep dives covering Web development, System Design, Open source projects, Tech industry trends and AI Engineering and tools.

Step 4: Install Axios in React

We’ll use Axios.

Install:

npm install axios

Simple.

Step 5: Define Feedback Payload Type

Create:

feedbackWebhook.ts

Add:

interface FeedbackWebhookArgs {
  userName: string;
  page: string;
  rating: string;
  reason: string;
  comments?: string;
  attachmentName?: string;
  attachmentURL?: string;
}

This keeps our payload typed and predictable.

Step 6: Build Adaptive Card Payload

This is where the magic happens.

const buildAdaptiveCard = (
  args: FeedbackWebhookArgs
): Record<string, unknown> => {
  const {
    userName,
    page,
    rating,
    reason,
    comments,
    attachmentName,
    attachmentURL,
  } = args;

  const isLike = rating === 'LIKE';
  const containerStyle = isLike ? 'good' : 'attention';
  const headerText = isLike
    ? '👍 App - User Feedback'
    : '👎 App - User Feedback';
  const submittedAt = new Intl.DateTimeFormat('en-US', {
    timeZone: 'America/Chicago',
    year: 'numeric',
    month: 'short',
    day: 'numeric',
    hour: 'numeric',
    minute: '2-digit',
    hour12: true,
    timeZoneName: 'short',
  }).format(new Date());
  const facts = [
    { title: '👤 Name', value: userName || 'Unknown' },
    { title: '📄 Page', value: page },
    { title: '⭐ Rating', value: isLike ? 'HELPFUL' : 'NOT HELPFUL' },
    { title: '🏷️ Reasons', value: reason || 'None provided' },
    { title: '🕐 Submitted', value: submittedAt },
  ];
  const actions = attachmentURL
    ? [
        {
          type: 'Action.OpenUrl',
          title: `🖼️ ${attachmentName ?? 'View Attachment'}`,
          url: attachmentURL,
        },
      ]
    : [];
  return {
    type: 'message',
    attachments: [
      {
        contentType: 'application/vnd.microsoft.card.adaptive',
        content: {
          $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
          type: 'AdaptiveCard',
          version: '1.2',
          body: [
            {
              type: 'Container',
              style: containerStyle,
              items: [
                {
                  type: 'TextBlock',
                  text: headerText,
                  weight: 'Bolder',
                  size: 'Medium',
                  wrap: true,
                },
              ],
            },
            {
              type: 'FactSet',
              facts,
            },
            ...(comments
              ? [
                  {
                    type: 'TextBlock',
                    text: `💬 ${comments}`,
                    wrap: true,
                  },
                ]
              : []),
          ],
          actions,
        },
      },
    ],
  };
};

This builds the rich Teams card. 

Not just text. Structured information. Clean. Readable. Actionable.

Step 7: Send Webhook

Now send it.

import axios from 'axios';

export const triggerFeedbackWebhook = async (
  args: FeedbackWebhookArgs
): Promise<void> => {
  const webhookUrl = process.env.REACT_APP_TEAMS_WEBHOOK_URL;
  if (!webhookUrl) {
    console.log('Webhook URL missing');
    return;
  }
  const payload = buildAdaptiveCard(args);
  try {
    await axios.post(webhookUrl, payload, {
      headers: {
        'Content-Type': 'application/json',
      },
    });
    console.log('Webhook sent successfully');
  } catch (error) {
    console.error('Failed to send webhook', error);
  }
};

Simple. Production-friendly.

Step 8: Trigger From React UI

Imagine this is your feedback component:

const handleFeedbackSubmit = async () => {
  await triggerFeedbackWebhook({
    userName: 'Vijay Deepak',
    page: '/dashboard',
    rating: 'DISLIKE',
    reason: 'UI is confusing',
    comments: 'Navigation is difficult to understand',
    attachmentName: 'Issue Screenshot',
    attachmentURL: 'https://example.com/screenshot.png',
  });
};

When triggered:

Your Teams channel instantly gets:

👎 User Feedback

With all metadata.

That’s real-time collaboration.

What Makes This Better?

Most developers send:

{
  "text": "Feedback received"
}

That works. But it’s weak.

Adaptive Cards give:

Better readability
Rich formatting
Action buttons
Structured facts
Attachments
Cleaner alerts

This matters when your channel gets noisy. Readable wins.

Real Use Cases

This pattern isn’t limited to feedback.

Use it for:

Product feedback

Like this article.

Error alerts

🔥 API failed

Deployment notifications

🚀 Build deployed

Payment failures

❌ Transaction failed

New customer signup

🎉 New user onboarded

Analytics reports

📊 Weekly usage summary

Security Best Practices

Never hardcode:

const webhookUrl = "..."

Use:

REACT_APP_TEAMS_WEBHOOK_URL=...

Even better: Use backend proxy.

Example:

React SPA → Node API → Teams Webhook

Why?

Because exposed webhooks can be abused.

Protect them Always.

Final Thoughts

The best products don’t just collect data. They route it to the right people instantly. That’s the real value here.

Your React app already knows:

  • what users like

  • what they hate

  • what breaks

  • what needs fixing

The only question is: Who sees it?

With Microsoft Teams webhooks:

The answer becomes: Everyone who needs to.

And that changes how fast teams move. Because feedback delayed is feedback wasted.

Ship smarter 🚀

Thank You for Reading!

I hope you found it helpful and informative. If you have any questions or feedback, feel free to leave a comment below. Your support and engagement mean a lot to me.

Happy Coding!

Reply

Avatar

or to participate