Building AI Agents on OpenAI with DNotifier: A Practical Integration Guide

Part of a series on running AI workflows across model providers — this one is about OpenAI specifically.
Somewhere around the third time a team rebuilds their retry logic, their session storage, and their "wait, how do we log what the model actually said" dashboard from scratch, they usually stop and ask a more useful question: why are we building infrastructure instead of building the product?
That question is exactly why DNotifier exists, and it's why we're writing this guide. If you're building on OpenAI — GPT-4o, GPT-5, the o-series reasoning models, whatever's newest by the time you read this — you already have a good model. What you might not have yet is the layer underneath it: memory that survives a page refresh, a way to test a prompt against three model variants before you ship it, a knowledge base your agent can actually search, and a dashboard that tells you what happened when something goes wrong at 11pm on a Friday.
This post walks through exactly how that layer works when OpenAI is the model doing the thinking. No fluff, no "AI is transforming everything" preamble. Just the setup, the code, and the decisions you'll actually have to make.
What DNotifier actually does between your app and OpenAI
DNotifier sits between your application code and the model provider. You write one integration — notifier.sendAI() — and DNotifier handles the parts that would otherwise be your problem: routing the request to the correct provider, attaching conversation history if there's a session involved, pulling relevant context from a knowledge base if you've connected one, and logging the whole exchange so it shows up in your dashboard instead of vanishing into a server log nobody reads until something breaks.
Here's the shape of it:
┌─────────────────────────┐
│ Your Application │
│ notifier.sendAI() │
└────────────┬────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ DNotifier Foundation Layer │
│ Routing │ Session Memory │ Knowledge Base │ Logging │
└────────────┬───────────────────────────────────────────────┘
│
▼
┌─────────────────────┐ ┌──────────────────────────────┐
│ OpenAI │ │ Anthropic, Gemini, Bedrock, │
│ (active provider) │ │ Ollama (alt providers) │
└─────────────────────┘ └──────────────────────────────┘
Your code calls sendAI() once. DNotifier's foundation layer handles routing, memory, retrieval, and logging — the OpenAI call itself happens behind that layer, not inside your application code.
The part worth sitting with is the bottom-right of that diagram. Anthropic, Gemini, Bedrock, Ollama — they're drawn as alternates because they're not being used in this example, not because they're unavailable. The same sendAI() call reaches whichever provider your app is configured for. If you start on OpenAI and later want to run a side-by-side comparison against Claude for a specific workflow, that's a configuration change, not a rewrite. We'll come back to why that matters more than it sounds like it should in a companion post — for now, the point is just that nothing about the code below locks you in.
Getting connected: the actual steps
1. Create your app and grab your credentials. Every DNotifier app gets an appId and a secret from the dashboard the moment you create it. These are what authenticate your SDK connection — keep the secret server-side, the same way you'd treat any API credential.
2. Add your OpenAI API key. In your app's settings, under the AI/Models section, you connect your OpenAI account by pasting in your API key. This is a one-time step per app. From here on, DNotifier's model router knows to send AI calls from this app to OpenAI unless you tell it otherwise.
3. Install the SDK.
npm install @dnotifier-realtime/dnotifier
(Python and Dart/Flutter SDKs exist too, if that's your stack — the concepts below map over directly.)
4. Connect and send your first prompt.
import { DNotifier } from "@dnotifier-realtime/dnotifier";
import WebSocket from "ws"; // only needed in Node.js
const notifier = new DNotifier({
appId: process.env.DNOTIFIER_APP_ID,
secret: process.env.DNOTIFIER_SECRET,
userId: "user_482",
transport: "ws",
WebSocketImpl: WebSocket,
});
await notifier.connect();
const response = await notifier.sendAI({
senderId: "user_482",
message: { text: "Summarize the attached invoice in two sentences." },
saveHistory: true,
});
console.log(response);That's the whole integration. No OpenAI SDK import, no Authorization: Bearer header, no handling the specific shape of a chat-completions response versus a responses-API response versus whatever OpenAI calls it next quarter. sendAI() is the interface. What's behind it is DNotifier's problem, not yours.
A quick note on that saveHistory: true flag, because it trips people up the first time: it's what tells DNotifier to persist this exchange as part of a conversation rather than treating it as a one-off. Pair it with a sessionId on multi-turn conversations and the model gets prior turns automatically — you're not manually reassembling a message array and stuffing it into every request the way you would calling a raw chat completions endpoint.
Where OpenAI specifically earns its keep here
We're not going to pretend every model is interchangeable for every job — that's not true, and DNotifier's whole "one API, every model" pitch would be hollow if it ignored that. OpenAI's lineup tends to be the default choice for a few specific jobs inside a DNotifier-powered app:
Tool-calling and structured output. If your agent needs to reliably return JSON that fits a schema, or decide between five possible function calls based on ambiguous user input, GPT-4o and the newer reasoning-tier models handle that with less prompt-wrangling than most alternatives. This matters a lot once you're past the demo stage and into something like the multi-agent support system we cover in a separate post in this series.
Cost-tiered routing inside one workflow. A pattern we see a lot: use a smaller, cheaper OpenAI model (something in the mini/nano tier) for intent classification or first-pass triage, then hand off to a larger model only for the step that actually needs the horsepower. Because DNotifier's workflow layer lets you assign a different model per agent within the same pipeline, you're not paying flagship-model prices to figure out which mailbox a support ticket belongs in.
Long-context summarization and document Q&A. Paired with DNotifier's built-in knowledge base (more on that below), OpenAI's models are a solid default for grounded question-answering over a document set — contracts, product docs, internal wikis, whatever your team has piled up.
None of this is a knock on other providers — it's just what tends to work well, today, for these specific jobs. Test it yourself; that's actually the next section.
Testing a prompt against multiple OpenAI models before you commit
One of the more underused parts of the platform is the Prompt Testing Studio — you write a single prompt, run it against several model configurations at once, and compare the outputs side by side along with cost and latency. If you're deciding between gpt-4o-mini and gpt-4o for a specific step in your pipeline, this is where you settle it with actual numbers instead of a hunch.
The workflow looks like this in practice: draft your prompt, pick the model variants you want to compare (this doesn't have to stay inside OpenAI's lineup — you can put a GPT-4o response next to a Claude response in the same test if you've connected both), run it against five or six representative inputs from your real use case, and look at where the outputs actually diverge. Most of the time the cheaper model is fine for 80% of inputs and the expensive one earns its cost on the hard 20%. You want to know which 20% before your users find out for you.
Memory: why "stateless" is the wrong default for most agents
Raw API calls to OpenAI are stateless by design — every request stands alone unless you build the history-tracking yourself. That's fine for a single Q&A tool. It falls apart fast for anything resembling a conversation, a support thread, or an agent that's supposed to remember what a user told it five minutes ago.
DNotifier's session memory handles this by tying conversation history to a sessionId. Pass the same session ID across calls and the platform assembles the right context automatically:
const sessionId = "support-ticket-9931";
await notifier.sendAI({
senderId: "user_482",
sessionId,
message: { text: "My order hasn't arrived yet." },
saveHistory: true,
});
// Later, same session — the model already has the earlier turn
await notifier.sendAI({
senderId: "user_482",
sessionId,
message: { text: "It's been eight days now, can you escalate this?" },
saveHistory: true,
});
The second call doesn't need to repeat the order context — it's already there. This sounds like a small convenience until you're three months into production and realize how much application code you didn't have to write to make it work.
Grounding answers in your own content
OpenAI's models are trained on a huge amount of general knowledge and precisely none of your company's internal documentation, pricing sheets, or last week's product update. That gap is what the knowledge base / RAG layer closes. You upload or connect your documents, DNotifier handles the chunking, embedding, and semantic search underneath, and your sendAI() calls can pull relevant context automatically — no separate vector database to provision, monitor, or pay for on top of everything else.
This is the difference between an agent that says "I don't have information on that" and one that actually answers from your refund policy, your API docs, or your onboarding guide — using OpenAI to reason over content that OpenAI itself has never seen.
Watching it work
Every sendAI() call, every token, every tool invocation shows up in the dashboard's observability view if logging is enabled on your app. When a user reports "the bot gave me a weird answer," you're not guessing — you can open the session, see the exact prompt that went out, the exact response that came back, and how long it took. This turns "something's wrong with the AI" from a shrug into an actual debugging session.
A minimal but real example: a documentation assistant
Putting the pieces together, here's roughly what a support-docs assistant looks like end to end:
const sessionId = `docs-${userId}-${Date.now()}`;
const response = await notifier.sendAI({
senderId: userId,
sessionId,
message: { text: userQuestion },
saveHistory: true,
});
// response.text pulls from your connected knowledge base automatically,
// and remembers the last several turns of this conversation on its own.Nine lines, roughly, and the model behind it is OpenAI's — but the memory, retrieval, and logging around it aren't things you had to write.
Frequently asked questions
Do I need to write any OpenAI-specific code?
No. Your application code calls sendAI(). Which provider actually processes that request is a setting on your app, not a parameter you manage in every call.
Can I use a specific OpenAI model, like GPT-4o versus a reasoning model, for different parts of my app?
Yes — this is set per agent inside a workflow, so a routing/classification step and a final-answer step can use different models entirely, even though both are OpenAI.
What happens if OpenAI has an outage?
That's a real limitation of choosing any single provider, OpenAI included, and it's worth being honest about it: if you've only connected one provider, an outage on their end is an outage for that part of your app. Because DNotifier's routing is provider-agnostic, teams that want failover connect a second provider and can redirect traffic without re-architecting anything — but that's a decision you make deliberately, not something that happens for free just by using DNotifier.
Is my data used to train OpenAI's models?
That depends entirely on the terms of your OpenAI account and API agreement, not on DNotifier — we're a routing and infrastructure layer, not a party to that agreement. Check OpenAI's current API data usage policy directly for the authoritative answer.
Does this work for voice, not just text?
Chat and text are what we've covered here, but the same session and memory model extends to DNotifier's realtime and chat infrastructure more broadly — voice assistants built on the platform use the same underlying primitives.
The Bottom Line
If you're currently calling OpenAI's API directly and starting to feel the weight of the infrastructure around it — memory, retrieval, observability, and eventually multi-agent coordination — this is the layer that's meant to take that weight off you. The docs have the full SDK reference if you want to go deeper, and the free tier is enough to get a real integration running today, not just a toy demo.
Next up in this series: what OpenAI's own Agents API changes about this picture, and where a model-agnostic control plane still earns its place even if you're all-in on GPT models.
Explore DNotifier's SDK at dnotifier.com and wire OpenAI through sendAI() without rebuilding memory, RAG, and logging from scratch.
DNotifier × OpenAI
Part 1 of 3
- Part 1Building AI Agents on OpenAI with DNotifier: A Practical Integration Guide
- Part 2OpenAI's Agents API vs. DNotifier: A Managed Harness or a Model-Agnostic Control Plane?
- Part 3Building a Production Multi-Agent Customer Support System with OpenAI and DNotifier
Related articles

OpenAI's Agents API vs. DNotifier: A Managed Harness or a Model-Agnostic Control Plane?
OpenAI's September 2026 Agents API is a serious option for OpenAI-only teams. This post lays out the real tradeoffs versus a model-agnostic control plane like DNotifier — without the spin.

Building a Production Multi-Agent Customer Support System with OpenAI and DNotifier
Part three of our OpenAI series: three specialized agents, shared workflow state, cost-tiered models, and a human approval gate — built with defineAgent and Workflow, not pseudocode.

Claude on DNotifier: Setup Guide
Part one of the DNotifier × Claude series — connect Anthropic through sendAI(), pick the right Claude model tier, and get session memory, RAG, and observability without building the plumbing yourself.