For AI agents: Find a documentation index at https://talkjs.com/llms.txt (full content at https://talkjs.com/llms-full.txt). Get a markdown version of any page by appending .md to its URL path.
Skip to content

PHP

PHP chat API with no WebSocket server to run

Your PHP app stays a normal request-response app. It signs a token for the logged-in user and syncs users over our REST API, with no chat package to install. The browser loads the chat UI from a CDN, and we run the real-time part: we store and deliver the messages and send the notifications.

.message {
  white-space: normal;
  overflow: hidden;
  border-radius: 0.75rem;
  border-width: 1px;
  border-style: solid;
  word-wrap: break-word;
  position: relative;
  display: inline-block;
  max-width: calc(100% - 6rem - 0.25rem - 0.25rem);
  border-color: #F2F2F2;
  background-color: #F2F2F2;
  color: #171717;
}

.has-avatar-spacer .message {
  max-width: calc(100% - 3rem - 0.25rem - 0.25rem);
}

.by-me .message {
  border-color: #171717;
  background-color: #171717; 
  color: #fff;
  margin-right: 0.50rem;
}
Dev A
Hey, I'm trying to lift state up from a child component, but I'm getting undefined errors. Any ideas?
8m ago
You
Are you passing the state and the setState function correctly as props?
6m ago
Dev A
I think so, yes.
5m ago
ok-hand
thinking
You
Maybe you're not binding the handler in the constructor. That might be the issue.
2m ago
Dev A
typing...
just now
Write a reply...
You
Hey, I’m having trouble accessing my account. It says my password is incorrect.
5m ago
SupportBot
Hi there! No problem — let’s get that sorted. Have you tried resetting your password using the 'Forgot password?' link?
4m ago
thinking
thumbs-up
You
Yes, but I haven’t received the reset email yet.
3m ago
SupportBot
Thanks for confirming. I’ve just sent a new reset link to your email. Please check your inbox and spam folder.
2m ago
SupportBot
Is there anything else I can help you with?
just now
Got it — checking my inbox now!

Who does what in your PHP chat

Here's the split. Only the first card is server code you write.

Your PHP code

Signs a token for the logged-in user with your secret key, and creates users and conversations through the REST API. It all happens inside your normal requests, so there's no extra process to run.

The browser

Loads the chat UI from the jsDelivr CDN as a web component and connects as the user your template names.

TalkJS

Runs the real-time connections, stores message history, and sends email, SMS and push notifications. It calls your PHP endpoint with signed webhooks when messages are sent or read.

Classtime
Decathlon
Riot Games
Hyundai

Add chat to a PHP page in four steps

Everything below is plain PHP and HTML. The only thing you install is a JWT library.

01

Put the chat in your PHP template

Add the stylesheet, the import map and one module script to the <head> of your layout. Then place a <t-chatbox> element where the chat should appear, with the logged-in user's ID echoed into user-id. It's our pre-built chat UI, loaded from the jsDelivr CDN.

<!-- In the <head> of your layout -->
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@talkjs/web-components@<COMPONENTS_VERSION>/default.css"
/>
<script type="importmap">
  {
    "imports": {
      "@talkjs/web-components": "https://cdn.jsdelivr.net/npm/@talkjs/web-components@<COMPONENTS_VERSION>",
      "@talkjs/core": "https://cdn.jsdelivr.net/npm/@talkjs/core@<CORE_VERSION>"
    }
  }
</script>
<script type="module" async>
  import "@talkjs/web-components";
</script>

<!-- Where the chat goes -->
<t-chatbox
  style="width: 400px; height: 600px;"
  app-id="<APP_ID>"
  user-id="<?= htmlspecialchars($user['id']) ?>"
  conversation-id="<?= htmlspecialchars($conversationId) ?>"
></t-chatbox>

Replace <COMPONENTS_VERSION> and <CORE_VERSION> with the current npm versions of @talkjs/web-components and @talkjs/core, and <APP_ID> with the app ID from the Settings page of your TalkJS dashboard. In Laravel, a Blade view echoes {{ auth()->id() }} the same way, and we have a separate page on Laravel chat with TalkJS. The JavaScript chat UI docs cover every option.

02

Sign the user's token in PHP

With authentication on, only users with a valid token can connect, so nobody can read or send messages as someone else. Sign the token on your server with the lcobucci/jwt library (composer require lcobucci/jwt). This is the PHP code from our docs: a JWT with tokenType set to user, your app ID as issuer, the user ID as subject and a one-day expiry, signed with HS256 and your secret key.

<?php
// Uses `lcobucci/jwt`: https://packagist.org/packages/lcobucci/jwt
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Token\Builder;
require 'vendor/autoload.php';

$tokenBuilder = new Builder(new JoseEncoder(), ChainedFormatter::default());
$algorithm = new Sha256();
$signingKey = InMemory::plainText("<SECRET_KEY>_GOES_HERE_REPLACING_THIS_TEXT");

$now = new DateTimeImmutable();
$token = $tokenBuilder
  ->withClaim('tokenType', 'user')
  ->issuedBy('<APP_ID>')
  ->relatedTo('<USER_ID>')
  ->expiresAt($now->modify('+1 days'))
  ->getToken($algorithm, $signingKey)
  ->toString();
echo $token;

Run it in your controller before the template renders. Use the logged-in user's ID where <USER_ID> is, and keep the result in $authToken instead of echoing it. Keep your secret key in an environment variable on the server and never put it in template output: it gives full admin access to your TalkJS account.

Then pass the token to the page with setToken, once, before the chat mounts. Replace the module script from step 1 with this one:

<script type="module" async>
  import "@talkjs/web-components";
  import { getTalkSession } from "@talkjs/core";

  getTalkSession({
    appId: "<APP_ID>",
    userId: <?= json_encode((string) $user['id']) ?>,
  }).setToken("<?= $authToken ?>");
</script>

When the chat loads without errors, turn on authentication (identity verification) under Security settings on the Settings page of your dashboard. New accounts start with it off. The example token expires after 24 hours, so for longer sessions, add a normal PHP endpoint that returns a fresh token. The authentication docs show how to do both.

03

Sync users and conversations over REST

Your users and conversations need to exist in TalkJS before they can chat. Create them from PHP with the REST API at https://api.talkjs.com. REST calls need an admin token: the same JWT setup, with tokenType set to admin and no subject. Our docs advise a new token for each request, valid for 30 seconds:

<?php
// Uses `lcobucci/jwt`: https://packagist.org/packages/lcobucci/jwt
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Token\Builder;
require 'vendor/autoload.php';

$tokenBuilder = new Builder(new JoseEncoder(), ChainedFormatter::default());
$algorithm = new Sha256();
$signingKey = InMemory::plainText("<SECRET_KEY>_GOES_HERE_REPLACING_THIS_TEXT");

$now = new DateTimeImmutable();
$token = $tokenBuilder
  ->withClaim('tokenType', 'admin')
  ->issuedBy('<APP_ID>')
  ->expiresAt($now->modify('+30 seconds'))
  ->getToken($algorithm, $signingKey)
  ->toString();
echo $token;

Wrap that code in a function, talkjs_admin_token(), that returns $token. Send the token as Authorization: Bearer, not the secret key itself, which is the legacy way. Then create or update the user with PUT /v1/{appId}/users/{userId} and the conversation with PUT /v1/{appId}/conversations/{conversationId}. Both calls are idempotent, so your login code can make them every time. Here's an example with curl:

<?php
// Example: TalkJS REST calls with curl.
// talkjs_admin_token() is the admin token code above, returning $token.
function talkjs_put(string $path, array $body): int
{
    $ch = curl_init('https://api.talkjs.com/v1/<APP_ID>' . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => 'PUT',
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . talkjs_admin_token(), // new token per request
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode($body),
        CURLOPT_RETURNTRANSFER => true,
    ]);
    curl_exec($ch);

    return curl_getinfo($ch, CURLINFO_RESPONSE_CODE); // 200 means success
}

// On login: create or update the user, then the conversation.
talkjs_put('/users/' . rawurlencode($user['id']), [
    'name' => $user['name'],
    'email' => [$user['email']],
]);
talkjs_put('/conversations/' . rawurlencode($conversationId), [
    'participants' => [(string) $user['id'], (string) $otherUserId],
]);

TalkJS returns HTTP 200 when a call succeeds, so check the status code, not the response body. If you sync all users and conversations over REST, you can turn off browser synchronization.

04

Receive webhooks in PHP

Turn on webhooks on the Settings page of your dashboard, and TalkJS sends your PHP endpoint a POST request with a JSON payload for events such as message.sent and message.read. Check the X-TalkJS-Signature header before you trust a request. It's an HMAC-SHA256 of the X-TalkJS-Timestamp value, a dot and the raw request body, in uppercase hex, signed with your secret key marked "use for webhooks".

<?php
// Example: a PHP endpoint that receives TalkJS webhooks.
$payload = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_TALKJS_SIGNATURE"] ?? "";
$timestamp = $_SERVER["HTTP_X_TALKJS_TIMESTAMP"] ?? "";
$secret = getenv("TALKJS_SECRET_KEY"); // your secret key marked "use for webhooks"

$expectedSignature = strtoupper(hash_hmac("sha256", "$timestamp.$payload", $secret));
if (!hash_equals($expectedSignature, $signature)) {
    http_response_code(401);
    exit;
}

$event = json_decode($payload);
if ($event->type === "message.sent") {
    // Store $event->data->message, or queue slower work for later.
}

http_response_code(200);

By default, TalkJS waits 5 seconds for a response, so answer 200 quickly and do slow work later. The code builds on our vanilla PHP webhook example on GitHub, which also shows the Laravel version of the lines that read the request.

Retreat Guru logo
We were able to offload all messaging to TalkJS — hugely simplifying our development of an otherwise complex stack.
Deryk Wenaus
CTO, Retreat Guru

Ready-made chat UI you can customize

The chat UI comes with three different layouts—that are highly customizable—which you can mix and match to suit your needs. You have complete control over the styling, the layout, and document structure of the chat messages and the possibility to show metadata.

Inbox

The TalkJS Inbox shows the chat history on the left, selected conversation on the right. It's designed to act as the messaging center of your app, usually living on its own page.

Chatbox

The TalkJS Chatbox is the right half of the Inbox UI mode. It takes up less space and is designed to be embedded in your app near an order confirmation, a booking, or user profile.

Popup

You can also show a conversation in a popup that only shows up when the user clicks a button somewhere. When shown, the popup is positioned on the bottom right of your page.

What you'd run yourself to build chat in PHP

A classic PHP script runs per request, sends its output and closes the connection. Live chat needs a WebSocket server that runs all the time and keeps connections open. With Laravel Reverb, that usually means a process manager such as Supervisor, a reverse proxy and a restart whenever your code changes. Past about 1,000 connections you need a different event loop than the default one, and to spread across several servers you need a central Redis server. Broadcasting events also needs a queue worker.

And that server only moves events. You'd still build the chat UI, history, unread counts, notifications and moderation on top of it. We've built all of those. Your PHP app stays request-response, you can be live in days, and our pricing counts monthly active users only.

FAQs

No, and you don't need one. The only Composer package you install is lcobucci/jwt, to sign a JSON Web Token for each user. The rest of your PHP side is ordinary HTTP calls to the TalkJS REST API to create users and conversations. The chat UI runs in the browser as a web component that loads from a CDN.

You don't need a PHP library for the chat itself. TalkJS gives you the chat UI as web components, <t-chatbox>, <t-inbox> and <t-conversation-list>, that any PHP template can render, and a REST API your PHP code calls with curl or any HTTP client. The only Composer package you add is lcobucci/jwt, to sign user tokens.

No. The chat UI in the browser connects straight to TalkJS, so your PHP app never holds a connection open. Keeping those connections open is our job. If you build chat yourself, it's yours: a long-running WebSocket server such as Laravel Reverb next to PHP, kept alive by a process manager like Supervisor.

In four steps. Add the chat web component to your PHP template from the jsDelivr CDN. Sign a JWT for the logged-in user with lcobucci/jwt. Create users and conversations with PUT calls to the TalkJS REST API. Then, to act on new messages, receive webhooks in a PHP endpoint and check their HMAC-SHA256 signature.

Yes. The TalkJS chatbox is a web component, <t-chatbox>, so any PHP template can render it, Laravel Blade views included. Three tags in the page head load it from the jsDelivr CDN, and your PHP echoes the logged-in user's ID into the element. You don't need npm or a build tool.

Yes. Put the TalkJS chat UI in your PHP template, and your users can message each other or your team in real time. TalkJS stores the message history, and users who miss a message can get an email, SMS or browser notification. Your PHP code only signs tokens and syncs users over REST.

On the Settings page of your TalkJS dashboard. The app ID goes into your template as the app-id attribute. The secret key starts with sk_test or sk_live and stays on your server. Use it to sign tokens and check webhook signatures, and never echo it into a page, because it gives full admin access to your TalkJS account.

Yes. TalkJS is free in development mode, with every feature, and the free account doesn't expire. You pay when you go live with real users. Paid plans start with Basic at $279 a month, billed monthly, priced per monthly active user with unlimited conversations and messages.