Django
Sign Ticketping identity tokens in Django with PyJWT. A complete view, URL and settings, plus Django REST framework.
You’ll add a small app with one view that signs a token for the signed-in user. It uses Django’s normal session login, so there’s nothing new to configure for auth.
Install PyJWT
pip install PyJWTAdd PyJWT to requirements.txt (or pyproject.toml) too. Ticketping needs only HS256, so you don’t need the crypto extra.
Add the secret to settings
Get the identity secret from Settings → API → Identity secret and set it in your environment:
TICKETPING_IDENTITY_SECRET=tpis_...Then read it in settings.py:
import os
TICKETPING_IDENTITY_SECRET = os.environ["TICKETPING_IDENTITY_SECRET"]os.environ[...] fails at startup if the variable is missing, which is better than failing on the first chat. If you use django-environ or similar, read it the same way you read your other secrets. It’s the same value in every environment.
Add the view
Create a ticketping package in your project with an empty __init__.py, and this view.
ticketping/views.py:
import time
import jwt
from django.conf import settings
from django.http import HttpResponse
from django.views.decorators.http import require_POST
@require_POST
def ticketping_token(request):
user = request.user
if not user.is_authenticated:
return HttpResponse(status=401)
claims = {"sub": str(user.pk), "exp": int(time.time()) + 300}
if user.email:
claims["email"] = user.email
if user.get_full_name():
claims["name"] = user.get_full_name()
token = jwt.encode(claims, settings.TICKETPING_IDENTITY_SECRET, algorithm="HS256")
return HttpResponse(token, content_type="text/plain")@require_POSTrefuses other methods with405.- Signed-out requests get
401, and the widget stays anonymous. subis the user’s primary key as a string. If your users have a stable public ID (a UUID orusername), you can use that instead, but never change it later: it’s how Ticketping finds the user’s conversations.emailandnameare left out when empty, becausenullor empty values are refused.- The token is returned as plain text, not JSON.
You don’t need to add the app to INSTALLED_APPS: it has no models or templates.
Add the URL
ticketping/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("api/ticketping-token", views.ticketping_token, name="ticketping-token"),
]Include it in your project’s urls.py:
from django.urls import include, path
urlpatterns = [
# ...your other routes
path("", include("ticketping.urls")),
]The route is now POST /api/ticketping-token. To use another path, change it here and in getToken.
Call it from your templates
In your base template, identify the signed-in user. Django’s CSRF protection applies to the POST, so send the token in the X-CSRFToken header:
{% if user.is_authenticated %}
<script>
window.Ticketping ||= (...args) => (Ticketping.q ||= []).push(args)
Ticketping('identify', {
userId: '{{ user.pk|escapejs }}',
email: '{{ user.email|escapejs }}',
name: '{{ user.get_full_name|escapejs }}',
getToken: async () => {
const res = await fetch('{% url "ticketping-token" %}', {
method: 'POST',
headers: { 'X-CSRFToken': '{{ csrf_token }}' }
})
if (!res.ok) throw new Error(`Ticketping token request failed (${res.status})`)
return res.text()
}
})
</script>
{% endif %}
<script src="https://widget.ticketping.com/v2/loader.js" data-key="pk_..." async></script>Replace pk_... with your publishable key from Settings → Widgets.
When the user signs out, call Ticketping('logout') so the next person on the same computer starts fresh. The simplest place is your logout form:
<form method="post" action="{% url 'logout' %}" onsubmit="Ticketping('logout')">
{% csrf_token %}
<button type="submit">Log out</button>
</form>If your frontend is a separate JavaScript app on the same domain, read the CSRF token from the csrftoken cookie instead: document.cookie.match(/csrftoken=([^;]+)/)?.[1]. Make sure the cookie is set, for example with @ensure_csrf_cookie on the view that serves the app.
Django REST framework
If your API uses DRF, use this view instead. IsAuthenticated refuses signed-out requests (with 401 or 403, depending on your authentication classes), and it works with whatever authentication DRF is set up with: sessions, tokens or JWTs.
ticketping/views.py:
import time
import jwt
from django.conf import settings
from django.http import HttpResponse
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
@api_view(["POST"])
@permission_classes([IsAuthenticated])
def ticketping_token(request):
user = request.user
claims = {"sub": str(user.pk), "exp": int(time.time()) + 300}
if user.email:
claims["email"] = user.email
if user.get_full_name():
claims["name"] = user.get_full_name()
token = jwt.encode(claims, settings.TICKETPING_IDENTITY_SECRET, algorithm="HS256")
return HttpResponse(token, content_type="text/plain")The URL is the same. Returning a plain HttpResponse keeps the body as raw text instead of a JSON string.
With SessionAuthentication, DRF enforces CSRF just like Django, so keep the X-CSRFToken header. With token authentication, send your usual Authorization header from getToken instead.
Frontend on another origin
If your frontend runs on another origin (for example app.acme.com calling api.acme.com):
- Use the full URL in
getToken, withcredentials: 'include'for cookie sessions, or yourAuthorizationheader for token auth. - Allow the frontend’s origin with credentials, for example with
django-cors-headers:CORS_ALLOWED_ORIGINS = ["https://app.acme.com"]andCORS_ALLOW_CREDENTIALS = True. - For cookie sessions, add the frontend’s origin to
CSRF_TRUSTED_ORIGINS.
Check that it works
- Run
python manage.py runserver, sign in, and open the browser console on one of your pages. - Run this and copy the token:
await (await fetch('/api/ticketping-token', {
method: 'POST',
headers: { 'X-CSRFToken': document.cookie.match(/csrftoken=([^;]+)/)?.[1] }
})).text()- Paste it into the token validator under Settings → API → Identity secret. It shows the claims, or says exactly what to fix.
- Reload and send a message. The dashboard shows it with the user’s name, a Verified badge and a TEST tag.
A 403 with an HTML page means the CSRF header is missing. A 401 means you’re not signed in. For anything the validator reports, see When a token is refused.
You can also test the view directly:
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
import jwt
SECRET = "test-secret-at-least-32-bytes-long"
@override_settings(TICKETPING_IDENTITY_SECRET=SECRET)
class TicketpingTokenTests(TestCase):
def test_signs_the_signed_in_user(self):
user = get_user_model().objects.create_user("ada", "ada@acme.com", "pw")
self.client.force_login(user)
res = self.client.post("/api/ticketping-token")
claims = jwt.decode(res.content, SECRET, algorithms=["HS256"])
self.assertEqual(claims["sub"], str(user.pk))
self.assertEqual(claims["email"], "ada@acme.com")
def test_refuses_signed_out_requests(self):
self.assertEqual(self.client.post("/api/ticketping-token").status_code, 401)Copy prompt for your AI coding agent
Add Ticketping identity verification (chat widget v2) to this Django project.
Docs index: https://ticketping.com/llms.txt
This page as Markdown: https://ticketping.com/docs/identity-django.md
1. Add PyJWT to the project's dependencies.
2. In settings, add TICKETPING_IDENTITY_SECRET = os.environ["TICKETPING_IDENTITY_SECRET"] (or read it the way
this project reads other secrets). Add TICKETPING_IDENTITY_SECRET= to the env example file. Never commit the value.
3. Create a ticketping package with views.py and urls.py exactly as on the docs page: a @require_POST view that
returns 401 when signed out, and otherwise HttpResponse(jwt.encode({"sub": str(user.pk), "exp": int(time.time()) + 300,
plus email and name only when non-empty}, settings.TICKETPING_IDENTITY_SECRET, algorithm="HS256"), content_type="text/plain").
If the project uses Django REST framework for its API, use the DRF variant from the page instead.
Route it at api/ticketping-token (name "ticketping-token") and include it in the root urls.py.
4. In the base template, for authenticated users only, add the queue stub and
Ticketping('identify', { userId, email, name, getToken }) where getToken POSTs to {% url "ticketping-token" %}
with the X-CSRFToken header and returns res.text(). Add the loader script with data-key="pk_..." before </body>
and ask me for the publishable key. Call Ticketping('logout') after sign-out.
5. Add a test like the one on the page.
6. Tell me how to verify: sign in on localhost, fetch a token from the console, and paste it into the
token validator at Settings → API → Identity secret.