How to Embed Viva Engage in a PHP Web App

Microsoft Fix Intermediate 18 min read Official Docs Grounded Updated April 20, 2026

Why This Is Happening

I've had this exact question come up from PHP developers more times than I can count , and every time, the frustration is the same. You search Microsoft's documentation for "embed Viva Engage in PHP," and you're met with a wall of Azure AD guides, OAuth 2.0 flows, and JavaScript SDK references that assume you're building a .NET app or a SharePoint integration. There's no official PHP SDK for Viva Engage. Microsoft never published one. That's the core of the problem.

Viva Engage , rebranded from Yammer in 2022, is Microsoft's enterprise social networking layer inside Microsoft 365. It lets employees post in communities, share knowledge, and run virtual town halls. The embed Viva Engage in PHP challenge comes from the fact that it's deeply tied to Azure Active Directory (now Microsoft Entra ID) for authentication. Every single API call needs a bearer token issued by Azure AD, which means before you can display a single community feed post, you need to implement a full OAuth 2.0 authorization code flow. For PHP developers used to simpler third-party API integrations, this is a significant gear shift.

There are actually two distinct approaches to Viva Engage PHP integration, and Microsoft's docs don't make it obvious which one you need:

  • The JavaScript Embed Widget, a client-side approach where a small script tag pulls in a community feed iframe directly from Microsoft's servers. No PHP backend calls needed for the display itself. Authentication is handled in the browser using the user's existing Microsoft 365 session.
  • The Viva Engage REST API with PHP, a server-side approach where your PHP code exchanges OAuth tokens and makes direct HTTPS calls to the https://www.yammer.com/api/v1/ endpoint family (yes, the base URL still uses the yammer.com domain even after the Viva Engage rebrand).

Which one you need depends entirely on your use case. Displaying a read-only community feed on a company intranet built in PHP? The JavaScript widget is your fastest path. Building something where PHP needs to post messages, retrieve specific thread data, or act on behalf of users programmatically? You need the full OAuth + REST API route. Most people who ask this question need the first approach but end up digging into the second, costing them days of wasted time.

Another reason this trips people up: the official Viva Engage developer documentation is scattered across two different Microsoft doc domains, the legacy Yammer developer docs and the newer Viva developer docs under learn.microsoft.com. They sometimes contradict each other on token scopes and endpoint behavior. I'll cut through that noise and tell you exactly what works in 2026.

Browse all Microsoft fix guides →

The Quick Fix, Try This First

If your goal is to embed Viva Engage community feeds visually into your PHP-rendered pages, without needing your PHP backend to make API calls, the JavaScript Embed Widget is the answer. This is the officially supported embed method and it works on any web page your PHP app generates, regardless of framework.

Here's what you do. In your PHP template (or the HTML your PHP outputs), drop this script tag right before your closing </body> tag:

<script type="text/javascript" src="https://c64.assets-yammer.com/assets/platform_embed.js"></script>

Then, in the exact spot in your page where you want the feed to appear, place this div:

<div id="embedded-feed"
     data-network-permalink="your-company.com"
     data-feed-type="group"
     data-feed-id="YOUR_GROUP_ID"
     data-default-group-feed="true"
     data-header="false"
     data-footer="false">
</div>

Replace your-company.com with your Viva Engage network's permalink (this is the domain your organization uses for Viva Engage, you can find it by logging into Viva Engage and looking at the URL). Replace YOUR_GROUP_ID with the numeric ID of the community you want to display. To find a group's ID, navigate to that community in Viva Engage; the number in the URL after /groups/ is the ID.

When a user visits your PHP page, the JavaScript widget will detect whether they're already signed into their Microsoft 365 account in that browser. If they are, it renders the feed automatically. If they're not, it displays a "Sign In" prompt inside the embed area. No PHP OAuth code. No access tokens to manage. The entire auth handshake happens client-side between the user's browser and Microsoft's servers.

This works reliably for company intranet scenarios where all users have Microsoft 365 accounts. It does not work for external users who don't have accounts in your tenant, for that scenario, you'd need the full API approach covered in the steps below.

Pro Tip
The data-feed-type attribute accepts three values: group (for a specific community), topic (for a hashtag feed), and user (for a specific user's activity). Most teams want group. Also, set data-header="false" and data-footer="false" to strip out Viva Engage's own chrome, it looks much cleaner embedded in your own UI.
1
Register an Azure AD App for Viva Engage API Access

Before a single line of PHP can talk to the Viva Engage REST API, you need an app registration in Azure Active Directory (Microsoft Entra ID). This is where most PHP developers lose an hour, so let me walk you through it precisely.

Open your browser and go to the Azure Portal at portal.azure.com. In the left sidebar, click Azure Active Directory (or search for "Entra ID" in the top search bar, Microsoft has been renaming things). Then click App registrations in the left menu, and click + New registration at the top.

Fill in the form:

  • Name: Something descriptive like "My PHP App – Viva Engage"
  • Supported account types: Choose "Accounts in this organizational directory only" for single-tenant, or "Accounts in any organizational directory" if your PHP app serves multiple Microsoft 365 tenants
  • Redirect URI: Select "Web" and enter the exact URL where Microsoft will send the OAuth callback, e.g. https://yourapp.com/auth/viva-callback.php

Click Register. You'll land on the app's overview page. Copy down the Application (client) ID and the Directory (tenant) ID, you need both in your PHP config. Now click Certificates & secrets in the left menu, then + New client secret. Give it a description, set an expiry (24 months is reasonable), and click Add. Copy the secret Value immediately, Microsoft only shows it once.

Next, click API permissions, then + Add a permission. Select Yammer from the list (it's still listed under Yammer, not Viva Engage). Under Delegated permissions, check user_impersonation, this is the scope that allows your app to act on behalf of a signed-in user. Click Add permissions. If your tenant requires admin consent, click Grant admin consent for [your org].

If everything worked, your API permissions list should show Yammer / user_impersonation with a green checkmark under the Status column.

2
Implement the OAuth 2.0 Authorization Flow in PHP

With your app registered, your PHP app needs to redirect users to Microsoft's authorization endpoint to get their consent and receive an authorization code. Here's the exact implementation.

Create a file called viva-login.php. This page redirects the user to Microsoft to sign in:

<?php
$tenantId     = 'YOUR_TENANT_ID';
$clientId     = 'YOUR_CLIENT_ID';
$redirectUri  = 'https://yourapp.com/auth/viva-callback.php';
$scope        = 'https://api.yammer.com/user_impersonation offline_access';
$state        = bin2hex(random_bytes(16));

session_start();
$_SESSION['oauth_state'] = $state;

$authUrl = "https://login.microsoftonline.com/{$tenantId}/oauth2/v2.0/authorize?"
    . http_build_query([
        'client_id'     => $clientId,
        'response_type' => 'code',
        'redirect_uri'  => $redirectUri,
        'response_mode' => 'query',
        'scope'         => $scope,
        'state'         => $state,
    ]);

header('Location: ' . $authUrl);
exit;

Now create viva-callback.php to handle the code Microsoft sends back and exchange it for tokens:

<?php
session_start();

$tenantId    = 'YOUR_TENANT_ID';
$clientId    = 'YOUR_CLIENT_ID';
$clientSecret = 'YOUR_CLIENT_SECRET';
$redirectUri = 'https://yourapp.com/auth/viva-callback.php';

// Validate state to prevent CSRF
if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['oauth_state']) {
    die('State mismatch, possible CSRF attack.');
}

if (isset($_GET['error'])) {
    die('Auth error: ' . htmlspecialchars($_GET['error_description']));
}

$tokenEndpoint = "https://login.microsoftonline.com/{$tenantId}/oauth2/v2.0/token";

$response = file_get_contents($tokenEndpoint, false, stream_context_create([
    'http' => [
        'method'  => 'POST',
        'header'  => 'Content-Type: application/x-www-form-urlencoded',
        'content' => http_build_query([
            'client_id'     => $clientId,
            'client_secret' => $clientSecret,
            'grant_type'    => 'authorization_code',
            'code'          => $_GET['code'],
            'redirect_uri'  => $redirectUri,
            'scope'         => 'https://api.yammer.com/user_impersonation offline_access',
        ]),
    ],
]));

$tokens = json_decode($response, true);
$_SESSION['viva_access_token']  = $tokens['access_token'];
$_SESSION['viva_refresh_token'] = $tokens['refresh_token'];
$_SESSION['viva_token_expiry']  = time() + $tokens['expires_in'];

header('Location: /dashboard.php');
exit;

After a successful callback, the user's access token is stored in the PHP session. The access token expires in 3600 seconds (1 hour), which is why you also capture the refresh token, covered in Step 4. If this works, your session will contain a valid bearer token you can use immediately.

3
Make Viva Engage REST API Calls from PHP

With a valid access token in hand, your PHP app can now talk directly to the Viva Engage REST API. The base URL for all API calls is https://www.yammer.com/api/v1/, yes, the old Yammer domain, not a viva.microsoft.com URL. Microsoft has kept this endpoint active through the rebrand and there's no announced deprecation date.

Create a reusable helper function in a file like viva-api.php:

<?php
function vivaApiGet(string $endpoint, string $accessToken): array {
    $url = 'https://www.yammer.com/api/v1/' . ltrim($endpoint, '/');

    $context = stream_context_create([
        'http' => [
            'method' => 'GET',
            'header' => [
                'Authorization: Bearer ' . $accessToken,
                'Accept: application/json',
            ],
        ],
        'ssl' => [
            'verify_peer' => true,
        ],
    ]);

    $response = file_get_contents($url, false, $context);
    if ($response === false) {
        throw new RuntimeException('Viva Engage API request failed for: ' . $endpoint);
    }
    return json_decode($response, true);
}

Now use it to retrieve messages from a specific community. The endpoint to get messages from a group is messages/in_group/GROUP_ID.json:

<?php
require 'viva-api.php';
session_start();

$token   = $_SESSION['viva_access_token'];
$groupId = 12345678; // your community's numeric ID

$data = vivaApiGet("messages/in_group/{$groupId}.json?threaded=true&limit=10", $token);

foreach ($data['messages'] as $msg) {
    echo '<p>' . htmlspecialchars($msg['body']['plain']) . '</p>';
}

Other useful API endpoints for Viva Engage PHP integration:

  • users/current.json, returns the authenticated user's profile
  • groups.json, lists all communities the user is a member of
  • messages/following.json, the user's following feed
  • messages/liked_by/current.json, messages the user has liked

To post a message via PHP, use a POST call to messages.json with a body parameter and a group_id. If it returns HTTP 201, the message was created successfully.

4
Handle Token Refresh and Session Expiry Gracefully

Access tokens from Azure AD expire after 3600 seconds, one hour. If your users stay on your PHP app longer than that (and they will), your API calls will start returning HTTP 401 Unauthorized. The fix is implementing automatic token refresh using the refresh token you captured in Step 2.

Add this token refresh function to your viva-api.php helper:

<?php
function refreshVivaToken(): bool {
    session_start();

    $tenantId     = 'YOUR_TENANT_ID';
    $clientId     = 'YOUR_CLIENT_ID';
    $clientSecret = 'YOUR_CLIENT_SECRET';

    if (empty($_SESSION['viva_refresh_token'])) {
        return false; // user must re-authenticate
    }

    $response = file_get_contents(
        "https://login.microsoftonline.com/{$tenantId}/oauth2/v2.0/token",
        false,
        stream_context_create([
            'http' => [
                'method'  => 'POST',
                'header'  => 'Content-Type: application/x-www-form-urlencoded',
                'content' => http_build_query([
                    'client_id'     => $clientId,
                    'client_secret' => $clientSecret,
                    'grant_type'    => 'refresh_token',
                    'refresh_token' => $_SESSION['viva_refresh_token'],
                    'scope'         => 'https://api.yammer.com/user_impersonation offline_access',
                ]),
            ],
        ])
    );

    if ($response === false) return false;

    $tokens = json_decode($response, true);
    if (empty($tokens['access_token'])) return false;

    $_SESSION['viva_access_token']  = $tokens['access_token'];
    $_SESSION['viva_refresh_token'] = $tokens['refresh_token'] ?? $_SESSION['viva_refresh_token'];
    $_SESSION['viva_token_expiry']  = time() + $tokens['expires_in'];

    return true;
}

Then wrap every API call with a freshness check. Add this before any API call in your pages:

<?php
session_start();

// Refresh if token expires within 5 minutes
if (empty($_SESSION['viva_access_token']) ||
    $_SESSION['viva_token_expiry'] < (time() + 300)) {

    if (!refreshVivaToken()) {
        header('Location: /viva-login.php'); // force re-auth
        exit;
    }
}

$token = $_SESSION['viva_access_token'];

The 300-second buffer (5 minutes) is intentional. It prevents a race condition where the token expires between the freshness check and the actual API call, something that's hard to debug and causes intermittent failures in production. I've seen this bite teams who check with a 0-second buffer.

Note: Refresh tokens themselves expire too, typically after 90 days of inactivity (Microsoft's default). When both tokens are expired, the user will need to go through the full login flow again. That's normal and expected behavior.

5
Render Viva Engage Data in Your PHP Templates

Now that your PHP backend can reliably fetch and refresh tokens and pull data from the Viva Engage REST API, the last step is rendering that data cleanly in your PHP templates. Here's a complete example of a community feed component you can drop into any PHP page:

<?php
require_once 'viva-api.php';
session_start();

$token   = $_SESSION['viva_access_token'];
$groupId = 12345678;

try {
    $feedData = vivaApiGet(
        "messages/in_group/{$groupId}.json?threaded=true&limit=20",
        $token
    );
    $messages = $feedData['messages'] ?? [];
} catch (RuntimeException $e) {
    $messages = [];
    error_log('Viva Engage API error: ' . $e->getMessage());
}
?>

<section class="viva-feed">
  <h2>Team Updates</h2>

  <?php if (empty($messages)): ?>
    <p class="viva-empty">No recent posts in this community.</p>
  <?php else: ?>
    <?php foreach ($messages as $msg): ?>
      <div class="viva-post">
        <div class="viva-author">
          <img src="<?= htmlspecialchars($msg['sender_type'] === 'user'
              ? ($msg['sender']['mugshot_url'] ?? '/img/default-avatar.png')
              : '/img/default-avatar.png') ?>"
               alt="Author avatar" width="40" height="40">
          <div>
            <strong><?= htmlspecialchars($msg['sender']['full_name'] ?? 'Unknown') ?></strong>
            <time datetime="<?= htmlspecialchars($msg['created_at']) ?>">
              <?= htmlspecialchars(date('M j, Y', strtotime($msg['created_at']))) ?>
            </time>
          </div>
        </div>
        <div class="viva-body">
          <?= nl2br(htmlspecialchars($msg['body']['plain'] ?? '')) ?>
        </div>
        <div class="viva-actions">
          <span>👍 <?= (int)($msg['liked_by']['count'] ?? 0) ?></span>
          <span>💬 <?= (int)($msg['thread']['stats']['updates'] ?? 0) ?> replies</span>
        </div>
      </div>
    <?php endforeach; ?>
  <?php endif; ?>
</section>

A few important security notes on this rendering code: always run user-generated content through htmlspecialchars() before outputting it, Viva Engage posts can contain angle brackets and other characters that would break your HTML or introduce XSS vulnerabilities. Never echo raw API response data directly. Cast numeric fields explicitly with (int) rather than assuming the API always returns integers, the API can return null for zero-count fields on newer communities.

If you see a blank section instead of posts, add var_dump($feedData) temporarily to inspect the raw API response. The messages key may be named differently depending on whether you hit a threaded or unthreaded endpoint.

Advanced Troubleshooting

Once you have the basics working, there are several edge cases and enterprise scenarios that trip up even experienced developers. Here's what I see most often.

Multi-Tenant PHP Applications

If your PHP application serves users from multiple Microsoft 365 organizations (common in SaaS products), your Azure AD app registration must use the "Accounts in any organizational directory" account type. In your OAuth redirect URL, replace the tenant-specific endpoint with the common endpoint:

// Single tenant:
https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/authorize

// Multi-tenant:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize

You'll also need to capture which tenant each user belongs to from the ID token claims and store it alongside their access token, because each tenant's Viva Engage network is separate.

CORS Errors When Calling the API from JavaScript

The Viva Engage REST API does not support CORS from arbitrary origins. If you try to call https://www.yammer.com/api/v1/ directly from browser-side JavaScript in your PHP app, you'll get a CORS block. All API calls must go through your PHP backend as a proxy. The JavaScript Embed Widget (Step 1 in the Quick Fix) is the exception, it's specifically designed for client-side use because it's served from Microsoft's CDN with appropriate CORS headers.

Event Viewer Diagnostics

If you're running your PHP app on a Windows Server with IIS and seeing auth failures, check the Windows Event Log under Windows Logs > Application. Look for Event ID 4625 (logon failure) or Event ID 1000 (application error). For PHP-specific errors on IIS, also check the PHP error log, typically at C:\inetpub\logs\LogFiles\. HTTP 401 errors from the Viva Engage API are almost always caused by a malformed Authorization header, add this debugging line temporarily: error_log('Bearer token: ' . substr($token, 0, 20) . '...'); to confirm the token is being passed correctly.

Conditional Access Policies Blocking Your App

In enterprise environments, Azure AD Conditional Access policies can block your PHP app's OAuth flow even when credentials are correct. You'll see error code AADSTS53003 or AADSTS50076 in the OAuth error response. The fix requires your Azure AD administrator to either add your app to a Conditional Access exclusion or configure a compliant redirect URI. This is not something you can fix in PHP, it's a tenant policy decision.

Viva Engage Network Not Provisioned

If the API returns a 403 with the message "The user is not authorized to perform this action" even with a valid token, it usually means the user's Microsoft 365 tenant doesn't have Viva Engage enabled or the user hasn't been provisioned. This is controlled in the Microsoft 365 admin center under Settings > Org Settings > Viva Engage. Your PHP app can't override this, it requires admin action at the tenant level.

When to Call Microsoft Support
If you've registered your app correctly, granted admin consent, confirmed the user has a Viva Engage license, and you're still getting persistent 401 or 403 errors with no clear AADSTS error code, that's when it's time to escalate. The issue may be tenant-level provisioning that's stuck, or a bug in the OAuth token issuance for the Yammer scope specifically. Open a support ticket through the Microsoft 365 admin center (not the Azure portal) and provide the correlation ID from the failed OAuth response, it's in the x-ms-request-id response header. Microsoft Support will need that ID to trace the issue on their end.

Prevention & Best Practices

Getting the embed Viva Engage in PHP integration working is one thing. Keeping it stable as Microsoft evolves the platform is another. Here's how to build this so it doesn't break on you in six months.

Never hard-code the Yammer API base URL as a constant in dozens of files. Microsoft has been slowly transitioning endpoints and while they've kept backward compatibility so far, that could change. Put the base URL in a single config file, one change fixes everything.

Store tokens encrypted, not in raw session storage. PHP sessions stored on disk are readable by any process running as the same user on the server. Use openssl_encrypt() to encrypt the access token before storing, and decrypt on retrieval. Yes, this is extra work. No, it's not optional if your app is accessible from the internet.

Set up a cron job to proactively refresh tokens for any server-side operations (like scheduled posting or digest emails). Don't wait for a 401 to trigger a refresh in a background job, the job will fail silently. Check token expiry at the start of every scheduled task.

Watch Microsoft's Viva changelog. The API versioning story for Viva Engage is messy, there's a v1 API (the current one, under yammer.com) and preview endpoints under graph.microsoft.com/beta. Microsoft has been moving Viva Engage functionality into Microsoft Graph, and the Graph endpoints will eventually become the canonical way to access Engage data. Keep an eye on the Microsoft 365 roadmap at roadmap.microsoft.com for Viva Engage API changes.

Test with a dedicated service account, not your personal admin account. Delegated OAuth tokens tied to a personal account break when that person leaves the company or changes their password. Use a dedicated service account with a Viva Engage license assigned specifically for your PHP app's API access.

Quick Wins
  • Cache API responses in PHP (APCu or Redis) for 60–120 seconds, community feeds don't change by the second and caching dramatically reduces your API call volume
  • Add ?include_counts=true to message API calls to get like/reply counts in a single request instead of multiple calls
  • Use the JavaScript Embed Widget for display and the REST API only when you need write operations, mixing both approaches is valid and common
  • Store your Client Secret in an environment variable ($_ENV['VIVA_CLIENT_SECRET']) or a secrets manager, never commit it to version control

Frequently Asked Questions

Does Viva Engage have an official PHP SDK or library?

No, Microsoft has never published an official PHP SDK for Viva Engage or Yammer. There are a few community-maintained packages on Packagist (searchable as "yammer php"), but most haven't been updated since the Viva Engage rebrand and they target older OAuth flows. You're better off implementing the OAuth 2.0 flow directly using PHP's built-in HTTP functions or a library like Guzzle, as shown in this guide. Microsoft's own sample code for Viva Engage is written in C#, JavaScript, and Python only.

The JavaScript embed widget shows a blank space, why isn't it loading?

The most common cause is a Content Security Policy (CSP) header on your PHP app blocking the external script or iframe. Check your browser's developer console for CSP violation errors. You need to add assets-yammer.com and *.yammer.com to your script-src and frame-src CSP directives. The second most common cause is that the user isn't signed into their Microsoft 365 account in that browser, the widget requires an active session. Third possibility: the data-feed-id group ID is wrong, which causes the widget to render empty silently rather than showing an error.

Can I embed Viva Engage for external users who don't have Microsoft 365 accounts?

Not with the standard embed approach. Viva Engage is an authenticated platform, every user must have a Microsoft 365 account in a tenant that has Viva Engage enabled. There is no public, unauthenticated read-only API. If you need to show Viva Engage content to external users, you'd need to use a server-side service account to fetch content and display it through your own PHP-rendered UI, effectively building your own frontend for the data. That's a more complex architecture and you'd need to review Microsoft's terms of service for that use case.

I'm getting error AADSTS70011, what does it mean?

AADSTS70011 means "The provided value for the input parameter 'scope' is not valid." In the context of Viva Engage PHP integration, this almost always means you've used the wrong scope string. The correct scope is https://api.yammer.com/user_impersonation, note the full URL format, not just user_impersonation on its own. If you're also requesting a refresh token (you should be), add offline_access as a second scope value separated by a space. Using Yammer.ReadWrite.All (the Microsoft Graph scope format) will also cause this error because Viva Engage hasn't fully migrated its permissions model to Graph yet.

How do I find the group ID for a Viva Engage community?

The easiest way: log into Viva Engage in your browser and navigate to the community you want. Look at the URL, it will contain something like /groups/12345678/feed. That number is the group ID. Alternatively, after you have the OAuth flow working, call https://www.yammer.com/api/v1/groups.json with your access token, it returns a JSON array of all communities the authenticated user belongs to, each with an id field. You can also search by name using https://www.yammer.com/api/v1/groups.json?letter=T&page=1 to list groups starting with a specific letter.

Will this break when Microsoft fully migrates Viva Engage to Microsoft Graph?

Eventually, yes, but Microsoft has a long track record of keeping legacy endpoints alive during transitions (the yammer.com API has been "legacy" for years). The safest thing you can do right now is abstract all your Viva Engage API calls behind a single PHP service class so that when Microsoft eventually flips the switch to Graph-only endpoints, you change the base URL and auth scope in one place rather than hunting through dozens of files. Microsoft will announce deprecation of the yammer.com endpoints well in advance through the Microsoft 365 Message Center and their developer blog. Subscribe to the Viva developer changelog at learn.microsoft.com to stay current.

Related Microsoft Fix Guides

H
Sai Kiran Pandrala
Our team includes certified Microsoft engineers, Azure architects, and system administrators with 10+ years of enterprise IT experience. Every guide is written from hands-on troubleshooting, not guesswork. We test every fix before publishing.