Plug DMA Connect into the new Next.js site
Written for the developer building dermatologymanagersassociation.org. The website stays the front door; DMA Connect is the system of record behind it. Four integration points cover everything MemberClicks does today with its website builder, without locking the site into the platform.
Hosted login and SSO handoff
The website never handles passwords. A Sign in link sends the visitor to the hosted login page and DMA Connect sends them back to where they were going.
- Link to
https://connect.dermatologymanagersassociation.org/loginwith anextparameter for the portal page to land on. - DMA Connect authenticates (email and password, magic link or Google Workspace for staff) and sets a session on its own domain.
- If the website needs to know the visitor is a member (for example to unlock a members-only page), call
POST /api/v1/members/verifyserver-side, or read the signeddma_sessioncookie shared across the.dermatologymanagersassociation.orgparent domain.
<!-- Anywhere on the marketing site -->
<a href="https://connect.dermatologymanagersassociation.org/login?next=/portal">Member sign in</a>
<!-- Deep link straight to a portal page after login -->
<a href="https://connect.dermatologymanagersassociation.org/login?next=/portal/registrations">My registrations</a>// app/components/member-button.tsx (on the website)
import Link from "next/link";
const CONNECT = process.env.NEXT_PUBLIC_DMA_CONNECT_URL; // https://connect.dermatologymanagersassociation.org
export function MemberButton({ next = "/portal" }: { next?: string }) {
return (
<Link href={`${CONNECT}/login?next=${encodeURIComponent(next)}`} className="btn btn-primary">
Member sign in
</Link>
);
}Embeddable widgets
Two drop-in widgets cover the pages the website needs most: an upcoming events list and a join call-to-action. Both read live data, inherit DMA branding and open the full flow in a new tab.
Option A: script tag (recommended)
Renders inline, sizes itself and picks up the page font. Attributes control the widget type, limit and event kind.
<!-- 1. Drop a container where the widget should render -->
<div data-dma-widget="events" data-limit="5" data-kind="all"></div>
<!-- 2. Load the embed script once, before </body> -->
<script async src="https://connect.dermatologymanagersassociation.org/embed.js"></script>Option B: iframe
Zero JavaScript on the host page. Works in any CMS block that accepts raw HTML.
<iframe
src="https://connect.dermatologymanagersassociation.org/embed/events"
title="Upcoming DMA events"
width="100%"
height="520"
style="border:0;border-radius:12px"
loading="lazy"
></iframe><iframe
src="https://connect.dermatologymanagersassociation.org/embed/join"
title="Join DMA"
width="100%"
height="420"
style="border:0;border-radius:12px"
loading="lazy"
></iframe>Prefer to own the markup? Render the events page as a server component on the website and call the API directly. Cached for five minutes with Next.js revalidate.
// app/events/page.tsx (on the website): server component, no client JS needed
export default async function EventsPage() {
const res = await fetch("https://connect.dermatologymanagersassociation.org/api/v1/events", { next: { revalidate: 300 } });
const { data } = await res.json();
return (
<ul>
{data.map((ev) => (
<li key={ev.id}>
<a href={ev.url}>{ev.name}</a> · {ev.startsOn}
{ev.ticketSummary.memberFrom !== null && <> · members from ${ev.ticketSummary.memberFrom}</>}
</li>
))}
</ul>
);
}REST API reference
Read-only JSON over HTTPS. Public endpoints need no key. Member verification takes the public API key from Admin, Website integration. All responses include permissive CORS headers so they can be called from the browser as well as the server.
| Method | Path | Returns | Auth |
|---|---|---|---|
| GET | /api/v1/events | Published public events, upcoming first. Filters: kind, upcoming=false, limit. | None |
| GET | /api/v1/events/[slug] | One event with tickets, agenda, speakers, sponsors and hotel info. 404 JSON if missing. | None |
| GET | /api/v1/membership-types | Active membership levels with price, term, benefits and a join URL. | None |
| POST | /api/v1/members/verify | { member, status, memberNumber?, expiresOn?, membershipTypeId? } for an email. No other personal data. | X-DMA-Api-Key |
Unsupported methods return 405 with an Allow header. Errors use { "error": { "code", "message" } }. Rate limit: 600 requests per minute per key or IP.
/api/v1/eventscurl https://connect.dermatologymanagersassociation.org/api/v1/events{
"data": [
{
"id": "ev_webinar_oct",
"slug": "manager-roundtable-2027-fee-schedule",
"name": "Manager Roundtable: 2027 Fee Schedule & Coding Updates",
"tagline": "What the proposed 2027 physician fee schedule means for dermatology reimbursement.",
"kind": "webinar",
"status": "published",
"startsOn": "2026-10-14",
"endsOn": "2026-10-14",
"timezone": "America/New_York",
"venueName": null,
"venueCity": null,
"isVirtual": true,
"ceuCredits": 1,
"capacity": 500,
"registration": { "open": true, "opensOn": "2026-09-10", "closesOn": "2026-10-14" },
"ticketSummary": { "count": 2, "memberFrom": 0, "nonMemberFrom": 49, "earlyBirdEndsOn": null, "currency": "USD" },
"url": "https://connect.dermatologymanagersassociation.org/events/manager-roundtable-2027-fee-schedule",
"registrationUrl": "https://connect.dermatologymanagersassociation.org/events/manager-roundtable-2027-fee-schedule/register"
},
{
"id": "ev_webinar_nov",
"slug": "year-end-hr-compliance-checklist",
"name": "Q4 Roundtable: Year-End HR & Compliance Checklist",
"kind": "webinar",
"startsOn": "2026-11-12",
"endsOn": "2026-11-12",
"isVirtual": true,
"ceuCredits": 1,
"registration": { "open": false, "opensOn": "2026-10-01", "closesOn": "2026-11-12" },
"ticketSummary": { "count": 2, "memberFrom": 0, "nonMemberFrom": 49, "earlyBirdEndsOn": null, "currency": "USD" },
"registrationUrl": "https://connect.dermatologymanagersassociation.org/events/year-end-hr-compliance-checklist/register"
},
{
"id": "ev_workshop_dec",
"slug": "aesthetic-marketing-workshop",
"name": "Aesthetic Marketing Workshop",
"kind": "workshop",
"startsOn": "2026-12-03",
"endsOn": "2026-12-03",
"isVirtual": true,
"ceuCredits": 3,
"registration": { "open": false, "opensOn": "2026-10-15", "closesOn": "2026-12-01" },
"ticketSummary": { "count": 2, "memberFrom": 79, "nonMemberFrom": 129, "earlyBirdEndsOn": null, "currency": "USD" },
"registrationUrl": "https://connect.dermatologymanagersassociation.org/events/aesthetic-marketing-workshop/register"
},
{
"id": "ev_conf_2027",
"slug": "2027-annual-conference",
"name": "2027 DMA Annual Conference",
"kind": "conference",
"startsOn": "2027-04-16",
"endsOn": "2027-04-17",
"venueName": "The Hotel at Avalon, Autograph Collection",
"venueCity": "Alpharetta, GA",
"isVirtual": false,
"ceuCredits": 12,
"capacity": 320,
"registration": { "open": true, "opensOn": "2026-09-15", "closesOn": "2027-04-10" },
"ticketSummary": { "count": 6, "memberFrom": 195, "nonMemberFrom": 355, "earlyBirdEndsOn": "2027-02-15", "currency": "USD" },
"registrationUrl": "https://connect.dermatologymanagersassociation.org/events/2027-annual-conference/register"
}
],
"meta": { "count": 4, "asOf": "2026-09-29", "upcomingOnly": true }
}/api/v1/events/[slug]curl https://connect.dermatologymanagersassociation.org/api/v1/events/2027-annual-conference{
"data": {
"id": "ev_conf_2027",
"slug": "2027-annual-conference",
"name": "2027 DMA Annual Conference",
"tagline": "Transforming the landscape of education and networking for dermatology and aesthetic practice teams.",
"kind": "conference",
"status": "published",
"startsOn": "2027-04-16",
"endsOn": "2027-04-17",
"timezone": "America/New_York",
"venueName": "The Hotel at Avalon, Autograph Collection",
"venueCity": "Alpharetta, GA",
"isVirtual": false,
"ceuCredits": 12,
"capacity": 320,
"registration": { "open": true, "opensOn": "2026-09-15", "closesOn": "2027-04-10" },
"ticketSummary": { "count": 6, "memberFrom": 195, "nonMemberFrom": 355, "earlyBirdEndsOn": "2027-02-15", "currency": "USD" },
"url": "https://connect.dermatologymanagersassociation.org/events/2027-annual-conference",
"registrationUrl": "https://connect.dermatologymanagersassociation.org/events/2027-annual-conference/register",
"description": "Two days of practical education for the people who run dermatology, plastic surgery and medical aesthetic practices. ...",
"heroColor": "#12385e",
"tracks": ["Managers", "Aesthetic", "Front & Back Office"],
"venueAddress": { "line1": "9000 Avalon Blvd", "city": "Alpharetta", "state": "GA", "zip": "30009" },
"hotelInfo": "A discounted room block at The Hotel at Avalon is available until March 20, 2027 at $219/night. Use group code DMA27 when booking.",
"tickets": [
{ "id": "tt_2027_mgr_member", "name": "Managers Track - Member", "description": "Friday and Saturday sessions, meals and reception.", "memberOnly": true, "track": "Managers", "price": 345, "earlyBird": true, "listPrice": 395, "earlyBirdEndsOn": "2027-02-15", "capacity": 180 },
{ "id": "tt_2027_mgr_nonmember", "name": "Managers Track - Non-member", "description": "Friday and Saturday sessions, meals and reception.", "memberOnly": false, "track": "Managers", "price": 495, "earlyBird": true, "listPrice": 545, "earlyBirdEndsOn": "2027-02-15", "capacity": 60 }
],
"sessions": [
{ "id": "s27_1", "title": "Welcome & State of the Association", "description": null, "track": "General", "day": "2027-04-16", "startTime": "08:30", "endTime": "09:00", "room": "Avalon Ballroom", "ceuCredits": 0, "speakers": ["Grant Kim"] },
{ "id": "s27_2", "title": "Mastering Insurance Denials & the Appeals Process, 2027 Edition", "description": "Denial trends, payer-specific appeal strategies and the KPIs a manager should review weekly.", "track": "Managers", "day": "2027-04-16", "startTime": "09:00", "endTime": "10:15", "room": "Avalon Ballroom A", "ceuCredits": 1.25, "speakers": ["Kendra Wallace, CPC"] }
],
"speakers": [{ "id": "sp_2", "name": "Kendra Wallace, CPC", "title": "Revenue Cycle Consultant", "company": "ClearClaim Consulting" }],
"sponsors": [{ "id": "sponsor_galderma", "name": "Galderma", "tier": "Platinum", "website": "https://www.galderma.com", "category": "Pharma", "boothNumber": "1" }],
"customQuestions": [
{ "id": "q_role", "label": "Primary role", "type": "select", "required": true, "options": ["Administrator / Practice Manager", "Office Manager", "Billing / Insurance", "Aesthetic staff", "Provider", "Vendor"] },
{ "id": "q_first", "label": "Is this your first DMA conference?", "type": "radio", "required": true, "options": ["Yes", "No"] }
]
}
}/api/v1/membership-typescurl https://connect.dermatologymanagersassociation.org/api/v1/membership-types{
"data": [
{
"id": "mt_manager",
"slug": "manager",
"name": "Manager Membership",
"description": "For administrators, practice managers and key personnel with direct management responsibility in a dermatology, plastic surgery or medical aesthetic practice.",
"price": 150,
"currency": "USD",
"term": "annual",
"gracePeriodDays": 30,
"autoRenewDefault": true,
"maxSeats": null,
"benefits": [
"Discounted registration to the DMA Annual Conference",
"Member-only resource library and conference recordings",
"Access to the DMA member directory and community forum",
"Monthly manager roundtable webinars with CEUs",
"Job board posting and resume bank",
"Salary and benchmark survey results"
],
"joinUrl": "https://connect.dermatologymanagersassociation.org/join?type=manager"
},
{ "id": "mt_practice", "slug": "practice", "name": "Practice Membership", "price": 425, "term": "annual", "maxSeats": 5, "joinUrl": "https://connect.dermatologymanagersassociation.org/join?type=practice" },
{ "id": "mt_aesthetic", "slug": "aesthetic", "name": "Aesthetic Staff Membership", "price": 95, "term": "annual", "maxSeats": null, "joinUrl": "https://connect.dermatologymanagersassociation.org/join?type=aesthetic" },
{ "id": "mt_partner", "slug": "industry-partner", "name": "Industry Partner", "price": 650, "term": "annual", "maxSeats": null, "joinUrl": "https://connect.dermatologymanagersassociation.org/join?type=industry-partner" }
],
"meta": { "count": 4 }
}/api/v1/members/verifycurl -X POST https://connect.dermatologymanagersassociation.org/api/v1/members/verify \
-H "Content-Type: application/json" \
-H "X-DMA-Api-Key: dma_pk_live_..." \
-d '{ "email": "melissa.carter@peachtreederm.com" }'{
"member": true,
"status": "active",
"memberNumber": "DMA-2019-0135",
"expiresOn": "2027-03-15",
"membershipTypeId": "mt_practice"
}
// Unknown email
{ "member": false, "status": "not-found" }Webhooks
DMA Connect posts a signed JSON event to the website whenever something the site cares about changes. Retries with backoff for 24 hours on non-2xx responses. The delivery log lives in the admin console.
| Event | When it fires | Payload data |
|---|---|---|
member.created | A membership application is approved and the member record goes live. | memberId, memberNumber, membershipTypeId, joinedOn |
member.renewed | Dues are paid and the expiry date moves forward, including auto-renewals. | memberId, membershipTypeId, expiresOn, invoiceId |
registration.created | Someone registers for an event, member or guest, paid or free. | registrationId, eventId, eventSlug, ticketTypeId, memberId?, amountPaid, confirmationCode |
invoice.paid | Any invoice is settled: dues, event tickets or sponsorships. | invoiceId, number, kind, memberId, total, paymentMethod, paidOn |
POST https://www.dermatologymanagersassociation.org/api/dma-webhook
Content-Type: application/json
X-DMA-Signature: t=1759152000,v1=5f1e0a6c... (HMAC-SHA256 of the raw body with your signing secret)
{
"id": "evt_01J9Q0X8K2",
"type": "registration.created",
"createdAt": "2026-09-29T14:12:08Z",
"data": {
"registrationId": "reg_01J9Q0X8",
"eventId": "ev_conf_2027",
"eventSlug": "2027-annual-conference",
"ticketTypeId": "tt_2027_mgr_member",
"memberId": "m_001",
"amountPaid": 345,
"confirmationCode": "DMA-7KQ2ZR"
}
}// app/api/dma-webhook/route.ts (on the website)
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(req: Request) {
const raw = await req.text();
const sig = req.headers.get("x-dma-signature") ?? "";
const v1 = sig.split(",").find((p) => p.startsWith("v1="))?.slice(3) ?? "";
const expected = createHmac("sha256", process.env.DMA_WEBHOOK_SECRET!).update(raw).digest("hex");
if (!v1 || !timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
return new Response("bad signature", { status: 401 });
}
const event = JSON.parse(raw);
switch (event.type) {
case "member.created": // add to the website newsletter list
case "member.renewed": // refresh cached member count on the home page
case "registration.created": // bump the "spots left" counter
case "invoice.paid": // sync to QuickBooks
}
return new Response("ok");
}Try it
These requests hit the route handlers running in this demo. They read the same seed data the rest of the app starts from.
Shared branding
The portal, admin console, emails and widgets use the same design tokens as this page: navy #12385e, teal #149c8c, gold #c9862b, Inter for body text and Plus Jakarta Sans for headings. The website can import the same token file so a member never notices the seam between the two.