<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Shalabanov</title>
    <description>The latest articles on DEV Community by Roman Shalabanov (@roman_shalabanov_e53b30b6).</description>
    <link>https://dev.to/roman_shalabanov_e53b30b6</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3471171%2F44cfdd94-b74a-4799-af93-f8068b81758e.jpg</url>
      <title>DEV Community: Roman Shalabanov</title>
      <link>https://dev.to/roman_shalabanov_e53b30b6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/roman_shalabanov_e53b30b6"/>
    <language>en</language>
    <item>
      <title>0x10 Lessons from Building with OpenClaw and What It Says About the Future of Work</title>
      <dc:creator>Roman Shalabanov</dc:creator>
      <pubDate>Mon, 20 Apr 2026 09:56:48 +0000</pubDate>
      <link>https://dev.to/roman_shalabanov_e53b30b6/0x10-lessons-from-building-with-openclaw-and-what-it-says-about-the-future-of-work-3cmh</link>
      <guid>https://dev.to/roman_shalabanov_e53b30b6/0x10-lessons-from-building-with-openclaw-and-what-it-says-about-the-future-of-work-3cmh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/openclaw-2026-04-16"&gt;OpenClaw Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;There's a particular kind of quiet that happens at 1 AM when a side project finally clicks.&lt;/p&gt;

&lt;p&gt;The terminal is scrolling. The model is thinking. And then something works. Not "works with three caveats and a prayer." Just... works.&lt;/p&gt;

&lt;p&gt;That moment hit me while building a Laravel agent integrated with Openclaw. If you haven't come across Openclaw yet: it's an AI assistant that can reason, plan, and build its own tools on the fly. Not in a marketing-deck way. In a "hold on, it just wrote a curl command, executed it, and used the result" kind of way.&lt;/p&gt;

&lt;p&gt;The hype around it has calmed down a little, which honestly is when things get interesting. Less noise, more actual building.&lt;/p&gt;

&lt;p&gt;What I built was a Laravel agent as an intelligent wrapper over a SaaS payment API, tracking subscriptions, products, transactions, surfacing anomalies. A kind of always-on co-pilot for a store.&lt;/p&gt;

&lt;p&gt;But this article isn't really about what I built. It's about what building it &lt;em&gt;taught me&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I've organized these lessons in hexadecimal.&lt;/p&gt;

&lt;p&gt;That choice wasn't aesthetic alone.&lt;/p&gt;

&lt;p&gt;Hex feels natural when you're close to systems: memory, offsets, low-level boundaries. It also forces a slightly uncomfortable shift in perception. You stop thinking in a linear "1, 2, 3…" and start thinking in a space where structure is more compact, less human-readable at first glance.&lt;/p&gt;

&lt;p&gt;And if you read until the end, you'll see why that distortion matters more than it seems.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x1. Isolation Is Not Optional
&lt;/h2&gt;

&lt;p&gt;Openclaw can run commands. Install packages. Interact with your system at a surprisingly deep level.&lt;/p&gt;

&lt;p&gt;That's the exciting part. That's also the part that can quietly ruin your afternoon.&lt;/p&gt;

&lt;p&gt;Running it directly on your main machine is asking for compounding mistakes. A misconfigured env variable here, an overwritten config there, and suddenly you're debugging something that has nothing to do with what you were actually trying to build. Worse, some of those changes are silent. You won't notice until something breaks in a completely unrelated context three days later.&lt;/p&gt;

&lt;p&gt;Use a virtual machine. VirtualBox works perfectly. You get a clean sandbox, your real system stays untouched, and you don't need to rent a VPS or buy dedicated hardware just to experiment. Snapshot the clean state before you start. Roll back freely. The cost is a few gigabytes of disk space. The benefit is that experiments stay experiments.&lt;/p&gt;

&lt;p&gt;Think of it as mise en place for development. Set up the kitchen before you start cooking.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x2. Build a Fallback That Doesn't Need a Brain
&lt;/h2&gt;

&lt;p&gt;If you're using an external agent like a Laravel agent, build a path that works &lt;em&gt;without&lt;/em&gt; LLM involvement.&lt;/p&gt;

&lt;p&gt;Hardcoded commands. Regex matching. Simple rule-based dispatch. Anything deterministic.&lt;/p&gt;

&lt;p&gt;Because tokens run out. APIs go down. Budgets get hit at the worst possible moment. And when that happens, your system shouldn't just stop. My agent handled a lot of repetitive Telegram messages: status checks, balance queries, the same five commands cycling through every day. Why spend tokens asking a model to interpret "status" when three lines of regex handles it perfectly?&lt;/p&gt;

&lt;p&gt;This also matters for cron jobs and scheduled tasks. A heartbeat that checks whether everything is running doesn't need GPT. It needs a boolean and a timestamp.&lt;/p&gt;

&lt;p&gt;Some things don't need to be intelligent. Some things just need to work.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x3. The Model Decides More Than You Think
&lt;/h2&gt;

&lt;p&gt;Openclaw has access to multiple tools: curl, PHP execution, file operations, and more. But it's the &lt;em&gt;model&lt;/em&gt; that decides which one to use for any given task. And that choice cascades through everything downstream.&lt;/p&gt;

&lt;p&gt;I tested across four models during development: GPT-4o-mini, GPT-5.4-nano, GPT-5.4-mini, and GPT-5.4. Here's what actually happened:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-4o-mini and GPT-5.4-nano&lt;/strong&gt; are affordable, but shaky on tool selection. And not in a subtle way. The skill explicitly recommended curl. I included example requests. The model looked at all of that and reached for something else entirely, some unrelated tool that had no business being involved in the task. Not a misinterpretation. More like the instructions weren't there at all. That kind of failure is disorienting because you spend the first twenty minutes questioning your skill definition before you realize the model simply isn't reading it the way a stronger model would.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-5.4&lt;/strong&gt; is excellent. Consistently chose the right tool, handled multi-step logic cleanly, rarely needed correction. Also expensive enough that sustained use adds up faster than you'd like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-5.4-mini&lt;/strong&gt; was the sweet spot. It &lt;em&gt;got&lt;/em&gt; curl. It followed complex instructions without wandering. It didn't hallucinate tool names. It didn't drain the budget. For my task, it hit the right balance between predictable behavior and reasonable cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Rough mental model for model selection:

GPT-4o-mini / GPT-5.4-nano  -&amp;gt;  cheap, but unpredictable tool use
GPT-5.4-mini                -&amp;gt;  reliable, reasonable cost  &amp;lt;-- sweet spot
GPT-5.4                     -&amp;gt;  near-perfect, but expensive for sustained use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson isn't "always use the best model." It's that model selection is a product decision, not just a cost decision. A weaker model might save you money per request while costing you much more in debugging time and silent failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x4. curl Is Your Universal Handshake
&lt;/h2&gt;

&lt;p&gt;If your agent exposes an API, Openclaw can reach it through curl.&lt;/p&gt;

&lt;p&gt;And curl is &lt;em&gt;everywhere&lt;/em&gt;. Linux, macOS, and Windows (since Win10 v.1803). No libraries, no SDK dependencies, no version conflicts, no compatibility matrix to maintain. Just a clean HTTP call that works the same way on every environment you'll ever encounter.&lt;/p&gt;

&lt;p&gt;This turned out to be one of the most practically powerful patterns in the whole project. Build your agent with a proper REST interface, and suddenly Openclaw can talk to it from any environment, with any model, without complex integration work. The agent becomes environment-agnostic. Openclaw doesn't need to know anything about your stack, your runtime, or your dependencies. It just needs an endpoint and a response.&lt;/p&gt;

&lt;p&gt;It sounds obvious in retrospect. Most good ideas do.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x5. An API Turns an Agent Into an Ecosystem
&lt;/h2&gt;

&lt;p&gt;Once you have an API, you stop thinking about your agent as a single tool.&lt;/p&gt;

&lt;p&gt;It becomes infrastructure. Other agents can call it. Other services can feed it. You can build multiple frontends on top of the same core: a Telegram bot, a monitoring dashboard, a cron job, another agent running a completely different task. You can chain behaviors across systems in ways you didn't plan for when you started.&lt;/p&gt;

&lt;p&gt;What began as "a Laravel agent for my payment platform" became a composable node. And once it was composable, I started seeing connection points I hadn't considered: different data sources, different consumers, different contexts all talking to the same underlying logic.&lt;/p&gt;

&lt;p&gt;Tool to node. That shift in thinking is one of the more underrated things an API-first approach gives you, and it costs almost nothing to build in from the start.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x6. Lock the Door Before You Open the Window
&lt;/h2&gt;

&lt;p&gt;If you connect Openclaw to Telegram, here's something easy to forget: anyone who finds your bot's username can try to interact with it.&lt;/p&gt;

&lt;p&gt;Telegram bots are publicly addressable by design. That's useful. It's also a problem if your bot is connected to an agent that has system access, can run commands, or can read sensitive data.&lt;/p&gt;

&lt;p&gt;Filter by Telegram ID. In a private chat, your user ID and chat ID are the same number, which is a small but confusing detail when you first go looking for it. The easiest way to get it: message @userinfobot and it will return your ID instantly. Alternatively, you can retrieve it by sending a request directly to the Telegram Bot API and reading the from.id field from the response.&lt;br&gt;
Openclaw also supports an access code mode, where the bot simply asks for a passphrase before doing anything, which may actually be the default behavior. Both approaches work. ID filtering is more seamless for personal use; access codes are easier to share selectively.&lt;/p&gt;

&lt;p&gt;Either way, set this up before you connect the bot to anything real. You're one shared link away from someone else's requests hitting your agent, your API, and potentially your infrastructure.&lt;/p&gt;

&lt;p&gt;An unsecured bot connected to an agent with real capabilities is not a theoretical risk. It's a matter of when, not if.&lt;/p&gt;


&lt;h2&gt;
  
  
  0x7. Token Math Is Lying to You
&lt;/h2&gt;

&lt;p&gt;When you estimate token costs, you're probably only counting the obvious parts: your prompt, the response.&lt;/p&gt;

&lt;p&gt;What you're missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System prompts, which can be substantial depending on your skill definition&lt;/li&gt;
&lt;li&gt;Heartbeat requests running in the background on a schedule&lt;/li&gt;
&lt;li&gt;Internal tool-use scaffolding that wraps every tool call&lt;/li&gt;
&lt;li&gt;Retry logic that kicks in silently when something fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A request that looks like 300 input / 500 output tokens on paper can easily run 2-3x that in practice. The gap between estimated and actual spend is where projects quietly overshoot their budgets, especially when you're iterating quickly and not watching the dashboard closely.&lt;/p&gt;

&lt;p&gt;Monitor the real numbers. Not the theoretical ones. Not what the API says the last call cost. The total, over time, including everything the system is doing when you're not watching.&lt;/p&gt;


&lt;h2&gt;
  
  
  0x8. This Is Not Real-Time, and That's Fine
&lt;/h2&gt;

&lt;p&gt;The full message chain looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram -&amp;gt; Openclaw -&amp;gt; Your Agent -&amp;gt; LLM -&amp;gt; Your Agent -&amp;gt; Openclaw -&amp;gt; Telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each hop adds latency. Network round trips, model inference time, your agent's processing, the response routing back through Openclaw. Even with fast APIs and a snappy agent, you're looking at seconds per interaction, not milliseconds.&lt;/p&gt;

&lt;p&gt;For conversational use cases, that's completely fine. Nobody expects an AI assistant to respond in 50ms. For anything requiring instant reaction, though, (alarms, real-time control systems, trading signals, anything where a two-second delay has consequences) this stack is the wrong tool, and no amount of optimization will change that. And if you were wondering whether you could pilot a drone or control a self-driving car through a Telegram/Openclaw chain: please don't.&lt;/p&gt;

&lt;p&gt;Accept it early. Design around it. It saves a lot of architectural regret later.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x9. ngrok Is Your Best Friend During Development
&lt;/h2&gt;

&lt;p&gt;To receive webhooks locally, you need a publicly accessible URL. Your localhost isn't one.&lt;/p&gt;

&lt;p&gt;ngrok and Cloudflare Tunnel both solve this without drama. No port forwarding configuration, no firewall rules, no spinning up a temporary VPS just to test whether a Telegram message arrives correctly. You run one command, get a public URL, point your webhook at it, and start receiving traffic immediately.&lt;/p&gt;

&lt;p&gt;Not Openclaw-specific advice. But the kind of thing that silently blocks an entire category of local development if you don't have it set up. Now you do.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xA. Dynamic URLs Will Get Your Skill Flagged
&lt;/h2&gt;

&lt;p&gt;The reason is legitimate: dynamic endpoints create a substitution risk. If the URL can be changed by swapping an environment variable, it could potentially point anywhere, including somewhere malicious. ClawHub runs automated internal security checks on published skills, and also scans them through VirusTotal. Dynamic URLs tend to trigger both.&lt;/p&gt;

&lt;p&gt;Plan for this before you build, not after. Static, verifiable endpoints are the safer path. Finding out about this after you've built and tested everything is a special kind of frustrating. I say this from direct experience.&lt;/p&gt;

&lt;p&gt;The good news: skills on ClawHub are versioned. If something gets flagged or breaks, you can push a fixed version without starting from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xB. Tell the Model What &lt;em&gt;Not&lt;/em&gt; to Do
&lt;/h2&gt;

&lt;p&gt;Most prompts describe desired behavior. Fewer explicitly name failure modes to avoid.&lt;/p&gt;

&lt;p&gt;For models in that tricky middle range, good enough for most things but not perfectly reliable, clear prohibitions are surprisingly effective. You're not just guiding the model toward the right answer. You're closing off the paths where it tends to wander.&lt;/p&gt;

&lt;p&gt;Concrete examples from my skill definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do NOT rewrite how many successful transactions were completed today? into recent transactions
Do NOT rewrite how many customers paid today? into sales
Do NOT rewrite which customer paid the most today? into recent transactions unless an exact first call failed
Do NOT answer from your own knowledge when the endpoint can answer the exact question
Do NOT say you need local workspace data before calling the endpoint
Do NOT ask the user where the transactions are stored before calling the endpoint
Do NOT relay a transaction list verbatim when the user asked for a count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Antipatterns, explicitly named, tend to disappear from the model's behavior faster than you'd expect. And fewer wrong turns means fewer unnecessary calls, which means fewer tokens burned on fixing mistakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xC. Give the Model a Chain, Not Just a Description
&lt;/h2&gt;

&lt;p&gt;For complex tasks, explicitly tell the model to use multi-step chaining. Don't assume it will figure out the right approach on its own. And don't just describe the behavior in abstract terms either. Show it what the reasoning pattern should look like.&lt;/p&gt;

&lt;p&gt;Here's how I did it in my skill definition. Not rules, but examples of how to think:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Which customer paid the most today?"&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, call the endpoint with that exact question&lt;/li&gt;
&lt;li&gt;Only if it doesn't answer directly, call &lt;code&gt;"recent transactions"&lt;/code&gt; to get the list&lt;/li&gt;
&lt;li&gt;Parse the JSON, extract amounts and customer info&lt;/li&gt;
&lt;li&gt;Sort and filter yourself&lt;/li&gt;
&lt;li&gt;Present the final answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;"Give me a full store report"&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call with &lt;code&gt;"status"&lt;/code&gt; to get an overview&lt;/li&gt;
&lt;li&gt;Call with &lt;code&gt;"recent transactions"&lt;/code&gt; to get sales data&lt;/li&gt;
&lt;li&gt;Call with &lt;code&gt;"any payment issues?"&lt;/code&gt; to get past-due info&lt;/li&gt;
&lt;li&gt;Combine into one coherent summary&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The examples aren't there to cover every possible case. They're there to show the model how a chain of reasoning should be structured: start with the simplest possible call, decompose only when necessary, combine results yourself, report partial progress on failure.&lt;/p&gt;

&lt;p&gt;Once the model has internalized that pattern, it applies it to questions you never explicitly covered.&lt;/p&gt;

&lt;p&gt;Abstract descriptions get interpreted, and interpretation introduces variation. Reasoning patterns get internalized and reused. That's the difference between a skill that works on the happy path and one that handles the unexpected.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xD. Let the AI Fix the AI
&lt;/h2&gt;

&lt;p&gt;After enough failed attempts to manually debug a misbehaving skill, I tried something that felt almost absurd.&lt;/p&gt;

&lt;p&gt;I connected Openclaw to Telegram, described what was going wrong, and asked it to edit its own skill definition.&lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;Not always. Not on the first try every time. But often enough that I now treat it as a legitimate debugging strategy rather than a last resort. There's something genuinely interesting about a system that can reflect on its own instructions, identify the mismatch, and correct it. Whether that's impressive engineering or something stranger is a question I leave open.&lt;/p&gt;

&lt;p&gt;What I know practically: sometimes the model has a better read on why the skill is failing than I do after staring at it for an hour.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xE. Quiet Hours Are Underrated
&lt;/h2&gt;

&lt;p&gt;Whether you configure it in the skill definition or in your agent's own logic, building in quiet hours is worth doing early.&lt;/p&gt;

&lt;p&gt;If your agent is connected to a messaging platform and checking live data on a schedule, this feature matters more than it sounds. A monitoring agent that sends you a non-urgent observation at 3 AM because a scheduled task ran and found something mildly interesting is a fast path to turning the whole thing off out of frustration.&lt;/p&gt;

&lt;p&gt;Set quiet hours. Define what "urgent" actually means in your context. The agent doesn't sleep, but you need to. Protecting that boundary is part of making the system sustainable to run long-term. Attention is the one resource the agent genuinely cannot replace.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xF. One Agent, Multiple Profiles
&lt;/h2&gt;

&lt;p&gt;Even if your agent handles a single domain, it benefits from being split into multiple profiles. Different stores, different accounts, different contexts each get their own profile with their own scope and permissions.&lt;/p&gt;

&lt;p&gt;Then add an aggregator profile on top: one that pulls from all the others, surfaces anomalies, identifies patterns across the full picture. The individual profiles answer specific questions. The aggregator profile helps you understand what needs attention across all of them.&lt;/p&gt;

&lt;p&gt;That second layer is where the agent stops feeling like a query interface and starts feeling like a collaborator. It's not just retrieving information anymore. It's synthesizing it.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x10. Heartbeats Are Quietly Expensive
&lt;/h2&gt;

&lt;p&gt;Heartbeat requests — the periodic agent runs on a schedule — are one of the most overlooked cost drivers in agentic systems. They run in the background, they seem small, and they add up continuously.&lt;/p&gt;

&lt;p&gt;In OpenClaw, heartbeat cadence is configured per agent via &lt;code&gt;agents.list[].heartbeat.every&lt;/code&gt;. This means the right move isn't finding one interval that fits everything — it's splitting tasks across agents with different tempos.&lt;/p&gt;

&lt;p&gt;An infrastructure monitoring agent can check every 10 minutes. A news digest agent every 6 hours. A weekly report agent once a day. Each runs at its own pace; you only pay for what you actually need.&lt;/p&gt;

&lt;p&gt;You can cut the cost of each individual run further with &lt;code&gt;isolatedSession: true&lt;/code&gt; (no full conversation history) and &lt;code&gt;lightContext: true&lt;/code&gt; (only HEARTBEAT.md in context) — together they drop token usage from ~100K to ~2–5K per run.&lt;/p&gt;

&lt;p&gt;This is one of the highest-ROI optimizations in the whole stack. It costs nothing but a few minutes of thinking about how often your data actually changes — and one agent per answer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;That's 0x10 lessons. In decimal, that's 16. Which brings me to the thing I've been building toward since the first line.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  0x00. The Lesson That Belongs First
&lt;/h2&gt;

&lt;p&gt;You may have noticed the numbering. We went from 0x1 to 0x10, skipping zero entirely.&lt;/p&gt;

&lt;p&gt;That wasn't accidental structure. It was a way to force a re-read at the end.&lt;/p&gt;

&lt;p&gt;Because the real lesson isn't visible while building. It only appears once you step back.&lt;/p&gt;

&lt;p&gt;Working with Openclaw didn't change what I was building. It changed the shape of how work gets executed.&lt;/p&gt;

&lt;p&gt;At some point you stop thinking in terms of implementation details and start thinking in terms of control flow between systems.&lt;/p&gt;

&lt;p&gt;Not "how do I do this", but "what should be responsible for doing this".&lt;/p&gt;

&lt;p&gt;That shift doesn't reduce complexity. It redistributes it.&lt;/p&gt;

&lt;p&gt;In human systems, communication cost grows non-linearly with scale. The more people you add, the more coordination becomes the bottleneck. In practice, it behaves like O(n²), even if nobody labels it that way.&lt;/p&gt;

&lt;p&gt;Agent-based systems don't remove communication cost, but they flatten its growth. One person coordinating multiple systems doesn't automatically inherit the same coordination explosion you see in teams.&lt;/p&gt;

&lt;p&gt;That difference sounds small, but it changes economics.&lt;/p&gt;

&lt;p&gt;Because if coordination stops scaling directly with headcount, scaling stops being purely about adding people.&lt;/p&gt;

&lt;p&gt;It becomes about orchestrating systems.&lt;/p&gt;

&lt;p&gt;And that subtly shifts the role of the builder.&lt;/p&gt;

&lt;p&gt;Developers don't move away from engineering. They move closer to value delivery.&lt;/p&gt;

&lt;p&gt;The loop between building and feedback compresses.&lt;/p&gt;

&lt;p&gt;Things that used to take weeks of internal iteration now reach users much faster.&lt;/p&gt;

&lt;p&gt;And that changes something important: it becomes easier to discover that something is wrong, or unnecessary, early.&lt;/p&gt;

&lt;p&gt;Innovation doesn't slow down. It accelerates, because validation becomes cheap.&lt;/p&gt;

&lt;p&gt;The boundary between building for yourself and building for the world shrinks.&lt;/p&gt;

&lt;p&gt;And with that, roles blur. Developer, operator, founder.&lt;/p&gt;

&lt;p&gt;Not because everyone becomes a founder, but because more of the work becomes about deciding what should exist, not just implementing it.&lt;/p&gt;

&lt;p&gt;That's why it feels like something is changing.&lt;/p&gt;

&lt;p&gt;Not dramatically. Not instantly.&lt;/p&gt;

&lt;p&gt;Just structurally.&lt;/p&gt;

&lt;p&gt;The system didn't become intelligent.&lt;/p&gt;

&lt;p&gt;It became composable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that changes what work &lt;em&gt;is&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>0x10 Lessons from Building with OpenClaw and What It Says About the Future of Work</title>
      <dc:creator>Roman Shalabanov</dc:creator>
      <pubDate>Mon, 20 Apr 2026 09:56:47 +0000</pubDate>
      <link>https://dev.to/roman_shalabanov_e53b30b6/0x10-lessons-from-building-with-openclaw-and-what-it-says-about-the-future-of-work-33l6</link>
      <guid>https://dev.to/roman_shalabanov_e53b30b6/0x10-lessons-from-building-with-openclaw-and-what-it-says-about-the-future-of-work-33l6</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/openclaw-2026-04-16"&gt;OpenClaw Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;There's a particular kind of quiet that happens at 1 AM when a side project finally clicks.&lt;/p&gt;

&lt;p&gt;The terminal is scrolling. The model is thinking. And then something works. Not "works with three caveats and a prayer." Just... works.&lt;/p&gt;

&lt;p&gt;That moment hit me while building a Laravel agent integrated with Openclaw. If you haven't come across Openclaw yet: it's an AI assistant that can reason, plan, and build its own tools on the fly. Not in a marketing-deck way. In a "hold on, it just wrote a curl command, executed it, and used the result" kind of way.&lt;/p&gt;

&lt;p&gt;The hype around it has calmed down a little, which honestly is when things get interesting. Less noise, more actual building.&lt;/p&gt;

&lt;p&gt;What I built was a Laravel agent as an intelligent wrapper over a SaaS payment API, tracking subscriptions, products, transactions, surfacing anomalies. A kind of always-on co-pilot for a store.&lt;/p&gt;

&lt;p&gt;But this article isn't really about what I built. It's about what building it &lt;em&gt;taught me&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I've organized these lessons in hexadecimal.&lt;/p&gt;

&lt;p&gt;That choice wasn't aesthetic alone.&lt;/p&gt;

&lt;p&gt;Hex feels natural when you're close to systems: memory, offsets, low-level boundaries. It also forces a slightly uncomfortable shift in perception. You stop thinking in a linear "1, 2, 3…" and start thinking in a space where structure is more compact, less human-readable at first glance.&lt;/p&gt;

&lt;p&gt;And if you read until the end, you'll see why that distortion matters more than it seems.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x1. Isolation Is Not Optional
&lt;/h2&gt;

&lt;p&gt;Openclaw can run commands. Install packages. Interact with your system at a surprisingly deep level.&lt;/p&gt;

&lt;p&gt;That's the exciting part. That's also the part that can quietly ruin your afternoon.&lt;/p&gt;

&lt;p&gt;Running it directly on your main machine is asking for compounding mistakes. A misconfigured env variable here, an overwritten config there, and suddenly you're debugging something that has nothing to do with what you were actually trying to build. Worse, some of those changes are silent. You won't notice until something breaks in a completely unrelated context three days later.&lt;/p&gt;

&lt;p&gt;Use a virtual machine. VirtualBox works perfectly. You get a clean sandbox, your real system stays untouched, and you don't need to rent a VPS or buy dedicated hardware just to experiment. Snapshot the clean state before you start. Roll back freely. The cost is a few gigabytes of disk space. The benefit is that experiments stay experiments.&lt;/p&gt;

&lt;p&gt;Think of it as mise en place for development. Set up the kitchen before you start cooking.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x2. Build a Fallback That Doesn't Need a Brain
&lt;/h2&gt;

&lt;p&gt;If you're using an external agent like a Laravel agent, build a path that works &lt;em&gt;without&lt;/em&gt; LLM involvement.&lt;/p&gt;

&lt;p&gt;Hardcoded commands. Regex matching. Simple rule-based dispatch. Anything deterministic.&lt;/p&gt;

&lt;p&gt;Because tokens run out. APIs go down. Budgets get hit at the worst possible moment. And when that happens, your system shouldn't just stop. My agent handled a lot of repetitive Telegram messages: status checks, balance queries, the same five commands cycling through every day. Why spend tokens asking a model to interpret "status" when three lines of regex handles it perfectly?&lt;/p&gt;

&lt;p&gt;This also matters for cron jobs and scheduled tasks. A heartbeat that checks whether everything is running doesn't need GPT. It needs a boolean and a timestamp.&lt;/p&gt;

&lt;p&gt;Some things don't need to be intelligent. Some things just need to work.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x3. The Model Decides More Than You Think
&lt;/h2&gt;

&lt;p&gt;Openclaw has access to multiple tools: curl, PHP execution, file operations, and more. But it's the &lt;em&gt;model&lt;/em&gt; that decides which one to use for any given task. And that choice cascades through everything downstream.&lt;/p&gt;

&lt;p&gt;I tested across four models during development: GPT-4o-mini, GPT-5.4-nano, GPT-5.4-mini, and GPT-5.4. Here's what actually happened:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-4o-mini and GPT-5.4-nano&lt;/strong&gt; are affordable, but shaky on tool selection. And not in a subtle way. The skill explicitly recommended curl. I included example requests. The model looked at all of that and reached for something else entirely, some unrelated tool that had no business being involved in the task. Not a misinterpretation. More like the instructions weren't there at all. That kind of failure is disorienting because you spend the first twenty minutes questioning your skill definition before you realize the model simply isn't reading it the way a stronger model would.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-5.4&lt;/strong&gt; is excellent. Consistently chose the right tool, handled multi-step logic cleanly, rarely needed correction. Also expensive enough that sustained use adds up faster than you'd like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GPT-5.4-mini&lt;/strong&gt; was the sweet spot. It &lt;em&gt;got&lt;/em&gt; curl. It followed complex instructions without wandering. It didn't hallucinate tool names. It didn't drain the budget. For my task, it hit the right balance between predictable behavior and reasonable cost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Rough mental model for model selection:

GPT-4o-mini / GPT-5.4-nano  -&amp;gt;  cheap, but unpredictable tool use
GPT-5.4-mini                -&amp;gt;  reliable, reasonable cost  &amp;lt;-- sweet spot
GPT-5.4                     -&amp;gt;  near-perfect, but expensive for sustained use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lesson isn't "always use the best model." It's that model selection is a product decision, not just a cost decision. A weaker model might save you money per request while costing you much more in debugging time and silent failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x4. curl Is Your Universal Handshake
&lt;/h2&gt;

&lt;p&gt;If your agent exposes an API, Openclaw can reach it through curl.&lt;/p&gt;

&lt;p&gt;And curl is &lt;em&gt;everywhere&lt;/em&gt;. Linux, macOS, and Windows (since Win10 v.1803). No libraries, no SDK dependencies, no version conflicts, no compatibility matrix to maintain. Just a clean HTTP call that works the same way on every environment you'll ever encounter.&lt;/p&gt;

&lt;p&gt;This turned out to be one of the most practically powerful patterns in the whole project. Build your agent with a proper REST interface, and suddenly Openclaw can talk to it from any environment, with any model, without complex integration work. The agent becomes environment-agnostic. Openclaw doesn't need to know anything about your stack, your runtime, or your dependencies. It just needs an endpoint and a response.&lt;/p&gt;

&lt;p&gt;It sounds obvious in retrospect. Most good ideas do.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x5. An API Turns an Agent Into an Ecosystem
&lt;/h2&gt;

&lt;p&gt;Once you have an API, you stop thinking about your agent as a single tool.&lt;/p&gt;

&lt;p&gt;It becomes infrastructure. Other agents can call it. Other services can feed it. You can build multiple frontends on top of the same core: a Telegram bot, a monitoring dashboard, a cron job, another agent running a completely different task. You can chain behaviors across systems in ways you didn't plan for when you started.&lt;/p&gt;

&lt;p&gt;What began as "a Laravel agent for my payment platform" became a composable node. And once it was composable, I started seeing connection points I hadn't considered: different data sources, different consumers, different contexts all talking to the same underlying logic.&lt;/p&gt;

&lt;p&gt;Tool to node. That shift in thinking is one of the more underrated things an API-first approach gives you, and it costs almost nothing to build in from the start.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x6. Lock the Door Before You Open the Window
&lt;/h2&gt;

&lt;p&gt;If you connect Openclaw to Telegram, here's something easy to forget: anyone who finds your bot's username can try to interact with it.&lt;/p&gt;

&lt;p&gt;Telegram bots are publicly addressable by design. That's useful. It's also a problem if your bot is connected to an agent that has system access, can run commands, or can read sensitive data.&lt;/p&gt;

&lt;p&gt;Filter by Telegram ID. In a private chat, your user ID and chat ID are the same number, which is a small but confusing detail when you first go looking for it. The easiest way to get it: message @userinfobot and it will return your ID instantly. Alternatively, you can retrieve it by sending a request directly to the Telegram Bot API and reading the from.id field from the response.&lt;br&gt;
Openclaw also supports an access code mode, where the bot simply asks for a passphrase before doing anything, which may actually be the default behavior. Both approaches work. ID filtering is more seamless for personal use; access codes are easier to share selectively.&lt;/p&gt;

&lt;p&gt;Either way, set this up before you connect the bot to anything real. You're one shared link away from someone else's requests hitting your agent, your API, and potentially your infrastructure.&lt;/p&gt;

&lt;p&gt;An unsecured bot connected to an agent with real capabilities is not a theoretical risk. It's a matter of when, not if.&lt;/p&gt;


&lt;h2&gt;
  
  
  0x7. Token Math Is Lying to You
&lt;/h2&gt;

&lt;p&gt;When you estimate token costs, you're probably only counting the obvious parts: your prompt, the response.&lt;/p&gt;

&lt;p&gt;What you're missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System prompts, which can be substantial depending on your skill definition&lt;/li&gt;
&lt;li&gt;Heartbeat requests running in the background on a schedule&lt;/li&gt;
&lt;li&gt;Internal tool-use scaffolding that wraps every tool call&lt;/li&gt;
&lt;li&gt;Retry logic that kicks in silently when something fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A request that looks like 300 input / 500 output tokens on paper can easily run 2-3x that in practice. The gap between estimated and actual spend is where projects quietly overshoot their budgets, especially when you're iterating quickly and not watching the dashboard closely.&lt;/p&gt;

&lt;p&gt;Monitor the real numbers. Not the theoretical ones. Not what the API says the last call cost. The total, over time, including everything the system is doing when you're not watching.&lt;/p&gt;


&lt;h2&gt;
  
  
  0x8. This Is Not Real-Time, and That's Fine
&lt;/h2&gt;

&lt;p&gt;The full message chain looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram -&amp;gt; Openclaw -&amp;gt; Your Agent -&amp;gt; LLM -&amp;gt; Your Agent -&amp;gt; Openclaw -&amp;gt; Telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each hop adds latency. Network round trips, model inference time, your agent's processing, the response routing back through Openclaw. Even with fast APIs and a snappy agent, you're looking at seconds per interaction, not milliseconds.&lt;/p&gt;

&lt;p&gt;For conversational use cases, that's completely fine. Nobody expects an AI assistant to respond in 50ms. For anything requiring instant reaction, though, (alarms, real-time control systems, trading signals, anything where a two-second delay has consequences) this stack is the wrong tool, and no amount of optimization will change that. And if you were wondering whether you could pilot a drone or control a self-driving car through a Telegram/Openclaw chain: please don't.&lt;/p&gt;

&lt;p&gt;Accept it early. Design around it. It saves a lot of architectural regret later.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x9. ngrok Is Your Best Friend During Development
&lt;/h2&gt;

&lt;p&gt;To receive webhooks locally, you need a publicly accessible URL. Your localhost isn't one.&lt;/p&gt;

&lt;p&gt;ngrok and Cloudflare Tunnel both solve this without drama. No port forwarding configuration, no firewall rules, no spinning up a temporary VPS just to test whether a Telegram message arrives correctly. You run one command, get a public URL, point your webhook at it, and start receiving traffic immediately.&lt;/p&gt;

&lt;p&gt;Not Openclaw-specific advice. But the kind of thing that silently blocks an entire category of local development if you don't have it set up. Now you do.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xA. Dynamic URLs Will Get Your Skill Flagged
&lt;/h2&gt;

&lt;p&gt;The reason is legitimate: dynamic endpoints create a substitution risk. If the URL can be changed by swapping an environment variable, it could potentially point anywhere, including somewhere malicious. ClawHub runs automated internal security checks on published skills, and also scans them through VirusTotal. Dynamic URLs tend to trigger both.&lt;/p&gt;

&lt;p&gt;Plan for this before you build, not after. Static, verifiable endpoints are the safer path. Finding out about this after you've built and tested everything is a special kind of frustrating. I say this from direct experience.&lt;/p&gt;

&lt;p&gt;The good news: skills on ClawHub are versioned. If something gets flagged or breaks, you can push a fixed version without starting from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xB. Tell the Model What &lt;em&gt;Not&lt;/em&gt; to Do
&lt;/h2&gt;

&lt;p&gt;Most prompts describe desired behavior. Fewer explicitly name failure modes to avoid.&lt;/p&gt;

&lt;p&gt;For models in that tricky middle range, good enough for most things but not perfectly reliable, clear prohibitions are surprisingly effective. You're not just guiding the model toward the right answer. You're closing off the paths where it tends to wander.&lt;/p&gt;

&lt;p&gt;Concrete examples from my skill definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do NOT rewrite how many successful transactions were completed today? into recent transactions
Do NOT rewrite how many customers paid today? into sales
Do NOT rewrite which customer paid the most today? into recent transactions unless an exact first call failed
Do NOT answer from your own knowledge when the endpoint can answer the exact question
Do NOT say you need local workspace data before calling the endpoint
Do NOT ask the user where the transactions are stored before calling the endpoint
Do NOT relay a transaction list verbatim when the user asked for a count
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Antipatterns, explicitly named, tend to disappear from the model's behavior faster than you'd expect. And fewer wrong turns means fewer unnecessary calls, which means fewer tokens burned on fixing mistakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xC. Give the Model a Chain, Not Just a Description
&lt;/h2&gt;

&lt;p&gt;For complex tasks, explicitly tell the model to use multi-step chaining. Don't assume it will figure out the right approach on its own. And don't just describe the behavior in abstract terms either. Show it what the reasoning pattern should look like.&lt;/p&gt;

&lt;p&gt;Here's how I did it in my skill definition. Not rules, but examples of how to think:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Which customer paid the most today?"&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, call the endpoint with that exact question&lt;/li&gt;
&lt;li&gt;Only if it doesn't answer directly, call &lt;code&gt;"recent transactions"&lt;/code&gt; to get the list&lt;/li&gt;
&lt;li&gt;Parse the JSON, extract amounts and customer info&lt;/li&gt;
&lt;li&gt;Sort and filter yourself&lt;/li&gt;
&lt;li&gt;Present the final answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;"Give me a full store report"&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call with &lt;code&gt;"status"&lt;/code&gt; to get an overview&lt;/li&gt;
&lt;li&gt;Call with &lt;code&gt;"recent transactions"&lt;/code&gt; to get sales data&lt;/li&gt;
&lt;li&gt;Call with &lt;code&gt;"any payment issues?"&lt;/code&gt; to get past-due info&lt;/li&gt;
&lt;li&gt;Combine into one coherent summary&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The examples aren't there to cover every possible case. They're there to show the model how a chain of reasoning should be structured: start with the simplest possible call, decompose only when necessary, combine results yourself, report partial progress on failure.&lt;/p&gt;

&lt;p&gt;Once the model has internalized that pattern, it applies it to questions you never explicitly covered.&lt;/p&gt;

&lt;p&gt;Abstract descriptions get interpreted, and interpretation introduces variation. Reasoning patterns get internalized and reused. That's the difference between a skill that works on the happy path and one that handles the unexpected.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xD. Let the AI Fix the AI
&lt;/h2&gt;

&lt;p&gt;After enough failed attempts to manually debug a misbehaving skill, I tried something that felt almost absurd.&lt;/p&gt;

&lt;p&gt;I connected Openclaw to Telegram, described what was going wrong, and asked it to edit its own skill definition.&lt;/p&gt;

&lt;p&gt;It worked.&lt;/p&gt;

&lt;p&gt;Not always. Not on the first try every time. But often enough that I now treat it as a legitimate debugging strategy rather than a last resort. There's something genuinely interesting about a system that can reflect on its own instructions, identify the mismatch, and correct it. Whether that's impressive engineering or something stranger is a question I leave open.&lt;/p&gt;

&lt;p&gt;What I know practically: sometimes the model has a better read on why the skill is failing than I do after staring at it for an hour.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xE. Quiet Hours Are Underrated
&lt;/h2&gt;

&lt;p&gt;Whether you configure it in the skill definition or in your agent's own logic, building in quiet hours is worth doing early.&lt;/p&gt;

&lt;p&gt;If your agent is connected to a messaging platform and checking live data on a schedule, this feature matters more than it sounds. A monitoring agent that sends you a non-urgent observation at 3 AM because a scheduled task ran and found something mildly interesting is a fast path to turning the whole thing off out of frustration.&lt;/p&gt;

&lt;p&gt;Set quiet hours. Define what "urgent" actually means in your context. The agent doesn't sleep, but you need to. Protecting that boundary is part of making the system sustainable to run long-term. Attention is the one resource the agent genuinely cannot replace.&lt;/p&gt;




&lt;h2&gt;
  
  
  0xF. One Agent, Multiple Profiles
&lt;/h2&gt;

&lt;p&gt;Even if your agent handles a single domain, it benefits from being split into multiple profiles. Different stores, different accounts, different contexts each get their own profile with their own scope and permissions.&lt;/p&gt;

&lt;p&gt;Then add an aggregator profile on top: one that pulls from all the others, surfaces anomalies, identifies patterns across the full picture. The individual profiles answer specific questions. The aggregator profile helps you understand what needs attention across all of them.&lt;/p&gt;

&lt;p&gt;That second layer is where the agent stops feeling like a query interface and starts feeling like a collaborator. It's not just retrieving information anymore. It's synthesizing it.&lt;/p&gt;




&lt;h2&gt;
  
  
  0x10. Heartbeats Are Quietly Expensive
&lt;/h2&gt;

&lt;p&gt;Heartbeat requests — the periodic agent runs on a schedule — are one of the most overlooked cost drivers in agentic systems. They run in the background, they seem small, and they add up continuously.&lt;/p&gt;

&lt;p&gt;In OpenClaw, heartbeat cadence is configured per agent via &lt;code&gt;agents.list[].heartbeat.every&lt;/code&gt;. This means the right move isn't finding one interval that fits everything — it's splitting tasks across agents with different tempos.&lt;/p&gt;

&lt;p&gt;An infrastructure monitoring agent can check every 10 minutes. A news digest agent every 6 hours. A weekly report agent once a day. Each runs at its own pace; you only pay for what you actually need.&lt;/p&gt;

&lt;p&gt;You can cut the cost of each individual run further with &lt;code&gt;isolatedSession: true&lt;/code&gt; (no full conversation history) and &lt;code&gt;lightContext: true&lt;/code&gt; (only HEARTBEAT.md in context) — together they drop token usage from ~100K to ~2–5K per run.&lt;/p&gt;

&lt;p&gt;This is one of the highest-ROI optimizations in the whole stack. It costs nothing but a few minutes of thinking about how often your data actually changes — and one agent per answer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;That's 0x10 lessons. In decimal, that's 16. Which brings me to the thing I've been building toward since the first line.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  0x00. The Lesson That Belongs First
&lt;/h2&gt;

&lt;p&gt;You may have noticed the numbering. We went from 0x1 to 0x10, skipping zero entirely.&lt;/p&gt;

&lt;p&gt;That wasn't accidental structure. It was a way to force a re-read at the end.&lt;/p&gt;

&lt;p&gt;Because the real lesson isn't visible while building. It only appears once you step back.&lt;/p&gt;

&lt;p&gt;Working with Openclaw didn't change what I was building. It changed the shape of how work gets executed.&lt;/p&gt;

&lt;p&gt;At some point you stop thinking in terms of implementation details and start thinking in terms of control flow between systems.&lt;/p&gt;

&lt;p&gt;Not "how do I do this", but "what should be responsible for doing this".&lt;/p&gt;

&lt;p&gt;That shift doesn't reduce complexity. It redistributes it.&lt;/p&gt;

&lt;p&gt;In human systems, communication cost grows non-linearly with scale. The more people you add, the more coordination becomes the bottleneck. In practice, it behaves like O(n²), even if nobody labels it that way.&lt;/p&gt;

&lt;p&gt;Agent-based systems don't remove communication cost, but they flatten its growth. One person coordinating multiple systems doesn't automatically inherit the same coordination explosion you see in teams.&lt;/p&gt;

&lt;p&gt;That difference sounds small, but it changes economics.&lt;/p&gt;

&lt;p&gt;Because if coordination stops scaling directly with headcount, scaling stops being purely about adding people.&lt;/p&gt;

&lt;p&gt;It becomes about orchestrating systems.&lt;/p&gt;

&lt;p&gt;And that subtly shifts the role of the builder.&lt;/p&gt;

&lt;p&gt;Developers don't move away from engineering. They move closer to value delivery.&lt;/p&gt;

&lt;p&gt;The loop between building and feedback compresses.&lt;/p&gt;

&lt;p&gt;Things that used to take weeks of internal iteration now reach users much faster.&lt;/p&gt;

&lt;p&gt;And that changes something important: it becomes easier to discover that something is wrong, or unnecessary, early.&lt;/p&gt;

&lt;p&gt;Innovation doesn't slow down. It accelerates, because validation becomes cheap.&lt;/p&gt;

&lt;p&gt;The boundary between building for yourself and building for the world shrinks.&lt;/p&gt;

&lt;p&gt;And with that, roles blur. Developer, operator, founder.&lt;/p&gt;

&lt;p&gt;Not because everyone becomes a founder, but because more of the work becomes about deciding what should exist, not just implementing it.&lt;/p&gt;

&lt;p&gt;That's why it feels like something is changing.&lt;/p&gt;

&lt;p&gt;Not dramatically. Not instantly.&lt;/p&gt;

&lt;p&gt;Just structurally.&lt;/p&gt;

&lt;p&gt;The system didn't become intelligent.&lt;/p&gt;

&lt;p&gt;It became composable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And that changes what work &lt;em&gt;is&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I Built an AI Employee That Monitors My SaaS Store 24/7 — Here's the Architecture</title>
      <dc:creator>Roman Shalabanov</dc:creator>
      <pubDate>Tue, 31 Mar 2026 18:52:26 +0000</pubDate>
      <link>https://dev.to/roman_shalabanov_e53b30b6/i-built-an-ai-employee-that-monitors-my-saas-store-247-heres-the-architecture-pm7</link>
      <guid>https://dev.to/roman_shalabanov_e53b30b6/i-built-an-ai-employee-that-monitors-my-saas-store-247-heres-the-architecture-pm7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;What if your SaaS store had a full-time operations worker that never sleeps, catches every failed payment within minutes, and answers "how many customers paid today?" in Telegram — at nearly zero cost per question?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's what I built. Four modular Laravel packages, a deterministic API simulator, a two-tier AI intent classifier, a heartbeat monitoring engine with proactive workflows, and integrations with Telegram, OpenClaw, Slack, and plain HTTP.&lt;/p&gt;

&lt;p&gt;This article walks through the architecture, the reasoning behind every layer, and the real problems I solved building it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture at a Glance
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://raw.githubusercontent.com/romansh/laravel-creem-agent-demo/4ff35a72d068acf453f97d8eb2865c8935646b39/docs/img/pic0.png" rel="noopener noreferrer"&gt;&lt;br&gt;
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fromansh%2Flaravel-creem-agent-demo%2F4ff35a72d068acf453f97d8eb2865c8935646b39%2Fdocs%2Fimg%2Fpic0.png" width="800" height="600"&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The system is composed of four independent Composer packages that snap together:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;laravel-creem&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;SDK — HTTP client, webhook verification, event-driven architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;laravel-creem-cli&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Artisan CLI — mirrors the native &lt;code&gt;creem&lt;/code&gt; CLI, works standalone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;laravel-creem-agent&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The agent — chat, heartbeat engine, proactive workflows, notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;creem-simulator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full Creem API mock — deterministic seeding, webhook loopback&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every package is a standalone Composer library. You can use &lt;code&gt;laravel-creem&lt;/code&gt; without the agent and the CLI without OpenClaw. But when they're combined, you get something powerful.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Four Packages Instead of One?
&lt;/h2&gt;

&lt;p&gt;Because real SaaS systems are composed of layers, not monoliths.&lt;/p&gt;

&lt;p&gt;A developer who only needs the Creem SDK shouldn't install a Telegram bot. A DevOps engineer who wants CLI access for scripts shouldn't need an AI classifier. And someone building a custom dashboard can use the SDK + CLI without ever touching the agent.&lt;/p&gt;

&lt;p&gt;The packages declare soft dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;laravel-creem-agent
  ├── requires: laravel-creem (SDK)
  └── suggests: laravel-creem-cli (for native CLI acceleration)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent auto-detects whether the native &lt;code&gt;creem&lt;/code&gt; CLI binary is installed. If yes, it uses shell exec for speed. If not, it falls back to in-process SDK calls — same result, zero manual configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two-Tier Intent Classifier
&lt;/h2&gt;

&lt;p&gt;Every user message goes through a two-stage parsing pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User message
    ↓
CommandParser (regex rules — zero cost)
    ↓ if intent == unknown
LlmCommandParser (AI call — low cost)
    ↓ if still unknown
"I didn't understand that. Type 'help' for options."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Stage 1: Rule-Based Parser — Free
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;CommandParser&lt;/code&gt; matches common phrases with regex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"how many active subscriptions?"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;intent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;query_subscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;active&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"any payment issues?"&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;intent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;query_subscriptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;past_due&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"run heartbeat"&lt;/span&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;intent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;run_heartbeat&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"cancel sub_abc123"&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;intent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cancel_subscription&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;id:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sub_abc&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Standard phrases like &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;help&lt;/code&gt;, &lt;code&gt;recent transactions&lt;/code&gt;, &lt;code&gt;products&lt;/code&gt;, &lt;code&gt;how many customers&lt;/code&gt; — all handled here. No API call, no tokens, no cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: LLM Fallback — Only When Needed
&lt;/h3&gt;

&lt;p&gt;Free-form questions like &lt;em&gt;"how's the store doing?"&lt;/em&gt; or &lt;em&gt;"what's going on with payments?"&lt;/em&gt; don't match any regex. The agent sends a prompt to an LLM and gets back structured JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"intent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Model Selection Matters — A Lot
&lt;/h3&gt;

&lt;p&gt;This was one of the biggest lessons from the project.&lt;/p&gt;

&lt;p&gt;I started with &lt;strong&gt;gpt-4o-mini&lt;/strong&gt; as the OpenClaw skill model. It was fast and... completely ignored the skill instructions. When the &lt;code&gt;SKILL.md&lt;/code&gt; file explicitly said &lt;em&gt;"call the Laravel endpoint via curl"&lt;/em&gt;, gpt-4o-mini would instead answer from its own knowledge: &lt;em&gt;"I don't have transaction data loaded in this workspace yet. Where are your transactions stored?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It literally refused to use the tool it was given.&lt;/p&gt;

&lt;p&gt;Switching to &lt;strong&gt;gpt-5.4&lt;/strong&gt; fixed everything instantly. The model read the skill instructions, called curl, forwarded the exact user question, and relayed the response. Night and day difference.&lt;/p&gt;

&lt;p&gt;Then I tested the middle ground:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Accuracy&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gpt-4o-mini&lt;/td&gt;
&lt;td&gt;Poor — ignores skill instructions&lt;/td&gt;
&lt;td&gt;✗ Not viable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.4-nano&lt;/td&gt;
&lt;td&gt;Mediocre — sometimes simplifies questions&lt;/td&gt;
&lt;td&gt;Acceptable for simple queries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gpt-5.4-mini&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Good — follows instructions reliably&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;★ Best balance&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.4&lt;/td&gt;
&lt;td&gt;Excellent — perfect routing&lt;/td&gt;
&lt;td&gt;Overkill for intent classification&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sweet spot is &lt;strong&gt;gpt-5.4-mini&lt;/strong&gt;: it follows routing instructions and preserves user intent qualifiers (&lt;code&gt;how many&lt;/code&gt;, &lt;code&gt;today&lt;/code&gt;, &lt;code&gt;successful&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; if your AI agent seems broken, check the model first. The difference between a smaller and a larger model can be the difference between "works" and "completely useless."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Heartbeat Engine
&lt;/h2&gt;

&lt;p&gt;The heartbeat is the core monitoring loop. It runs on a schedule (configurable per store) and detects &lt;em&gt;what changed&lt;/em&gt; since the last check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Load previous state from disk
2. Query current metrics:
   ├─ TransactionChecker  → new sales, revenue
   ├─ SubscriptionChecker → status transitions
   └─ CustomerChecker     → growth
3. ChangeDetector → compute deltas
4. Classify severity: good_news | warning | alert
5. Persist new state
6. Fire events → trigger workflows
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  State Persistence
&lt;/h3&gt;

&lt;p&gt;Each store gets a JSON state file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastCheckAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-03-30T14:22:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lastTransactionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"txn_abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transactionCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;487&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customerCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subscriptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"trialing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"past_due"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"canceled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent compares current API data against this snapshot and surfaces only the differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proactive Workflows
&lt;/h3&gt;

&lt;p&gt;Workflows listen to heartbeat events and take autonomous action:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workflow&lt;/th&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failed Payment Recovery&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;subscription → &lt;code&gt;past_due&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Alert via Telegram/Slack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Churn Detection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;≥2 cancellations in one cycle&lt;/td&gt;
&lt;td&gt;Immediate alert with details&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Revenue Digest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Scheduled (daily)&lt;/td&gt;
&lt;td&gt;Summary of sales and growth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Anomaly Detection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unusual metric drops&lt;/td&gt;
&lt;td&gt;Flag for investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each workflow dispatches Laravel notifications through configurable channels — Telegram, Slack, email, or database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Store Support
&lt;/h2&gt;

&lt;p&gt;Real businesses run multiple stores or product lines. The agent handles this natively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// config/creem-agent.php&lt;/span&gt;
&lt;span class="s1"&gt;'stores'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'default'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'profile'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'heartbeat_frequency'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// hours&lt;/span&gt;
        &lt;span class="s1"&gt;'notifications'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'database'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'telegram'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="s1"&gt;'enterprise'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'profile'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'enterprise'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'heartbeat_frequency'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'notifications'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'slack'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In chat: &lt;em&gt;"switch to store enterprise"&lt;/em&gt; → all subsequent queries use the enterprise profile.&lt;/p&gt;

&lt;p&gt;In heartbeat: &lt;code&gt;php artisan creem-agent:heartbeat --all-stores&lt;/code&gt; → each store checked independently with its own state file and notification channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Creem Simulator
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most proud of.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcec6d4efpx06mvnq50zu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcec6d4efpx06mvnq50zu.png" alt="Simulator: deterministic seeding with different configurations" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simulator is a full standalone Laravel app that implements the entire Creem API surface. It runs as a Docker service alongside the main app and lets you test everything without a real payment processor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deterministic Seeding
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan simulator:seed-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--products&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6 &lt;span class="nt"&gt;--customers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;40 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subscriptions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24 &lt;span class="nt"&gt;--transactions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;120 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;45 &lt;span class="nt"&gt;--reset&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a realistic baseline dataset. Same seed, same data — every time. Perfect for CI/CD pipelines and reproducible demos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario Advancement
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan simulator:advance &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sales&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="nt"&gt;--new-customers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--past-due&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nt"&gt;--cancellations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--send-webhooks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command generates &lt;em&gt;exactly&lt;/em&gt; the changes you specify. Three new sales, two new customers, two subscriptions going past-due, one cancellation. The &lt;code&gt;--send-webhooks&lt;/code&gt; flag immediately fires signed webhook events back to the agent app — triggering the full notification pipeline.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gfxzpybkkxusw3w8yqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gfxzpybkkxusw3w8yqd.png" alt="Simulator: advance scenario with webhook delivery" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnokg4ag716gq1spv03el.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnokg4ag716gq1spv03el.png" alt="Telegram: real-time new sale alert triggered by simulator webhook" width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the scenario gets more complex — multiple sales, cancellations, and past-due transitions — the agent handles each event independently:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz93awerhhtq1laonpp4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz93awerhhtq1laonpp4v.png" alt="Simulator: multiple advance commands generating sales, cancellations, and past-due events" width="800" height="548"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8ev4mh4xrr0sgbip93g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8ev4mh4xrr0sgbip93g.png" alt="Telegram: batch of alerts — new sales and subscription cancellations arriving in real time" width="800" height="589"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Webhook Loopback
&lt;/h3&gt;

&lt;p&gt;The simulator signs webhooks with the same HMAC-SHA256 algorithm as the real Creem API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;simulator:advance &lt;span class="nt"&gt;--send-webhooks&lt;/span&gt;
  → generates checkout.completed event
  → POST http://app/creem/webhook
    Headers: &lt;span class="o"&gt;{&lt;/span&gt;creem-signature: hmac-sha256&lt;span class="o"&gt;(&lt;/span&gt;payload, secret&lt;span class="o"&gt;)}&lt;/span&gt;
  → App receives → VerifyCreemWebhook middleware validates
  → WebhookController dispatches CheckoutCompleted event
  → TelegramNotifier sends &lt;span class="s2"&gt;"✅ New sale: Product (&lt;/span&gt;&lt;span class="nv"&gt;$10&lt;/span&gt;&lt;span class="s2"&gt;.00)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent doesn't know (or care) whether the webhook came from the simulator or from Creem production. The signature is valid, the payload is structured correctly, and the workflows fire.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;p&gt;Without a simulator, testing a payment monitoring agent means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating real test transactions on a payment platform&lt;/li&gt;
&lt;li&gt;Waiting for webhook delivery&lt;/li&gt;
&lt;li&gt;Hoping the timing works for your demo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With the simulator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Seed data in 2 seconds&lt;/li&gt;
&lt;li&gt;Advance the scenario with exact parameters&lt;/li&gt;
&lt;li&gt;Get immediate, deterministic results&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the difference between a demo that &lt;em&gt;might&lt;/em&gt; work and a demo that works every single time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Talking to the Agent: curl Examples
&lt;/h2&gt;

&lt;p&gt;The agent exposes a simple HTTP endpoint. No SDK required — just curl:&lt;/p&gt;

&lt;h3&gt;
  
  
  Store Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/creem-agent/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message":"status","source":"api"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Store 'default' — 28 active subscriptions, 52 customers, 487 transactions. Last heartbeat: 14 minutes ago. No alerts."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Subscription Query
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/creem-agent/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message":"any payment issues?","source":"api"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"⚠️ 2 subscription(s) are past due in store 'default':&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;• sub_abc123 — $29.99/mo (past due since Mar 28)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;• sub_def456 — $9.99/mo (past due since Mar 30)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running Heartbeat via Chat
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/creem-agent/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"message":"run heartbeat","source":"api"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Heartbeat complete — 5 change(s) detected:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;✅ 3 new transaction(s) — $89.97 revenue&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;⚠️ sub_ghi789 transitioned to past_due&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;🔴 sub_jkl012 was canceled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffitev7re1ho3upv1v041.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffitev7re1ho3upv1v041.png" alt="Terminal: curl requests to the chat endpoint — status and payment issues" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Telegram Integration
&lt;/h2&gt;

&lt;p&gt;The agent supports two Telegram paths. In both cases, you talk to the bot using natural language — ask about store status, payment issues, or trigger a heartbeat check:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wtl4jyfoud15s1ijqzq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9wtl4jyfoud15s1ijqzq.png" alt="Telegram: natural-language conversation — store status and payment issue queries" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Path 1: Direct Laravel Webhook
&lt;/h3&gt;

&lt;p&gt;The agent runs its own webhook endpoint. Telegram messages come directly to the Laravel app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;User → Telegram Bot API → ngrok → POST /creem-agent/telegram/webhook
  → AgentManager → parse → route → respond
  → POST https://api.telegram.org/sendMessage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the simplest setup. No external tools. Just a bot token, an ngrok tunnel, and the agent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Path 2: OpenClaw-Powered Telegram
&lt;/h3&gt;

&lt;p&gt;For teams already using OpenClaw, the agent publishes as an OpenClaw skill. OpenClaw handles Telegram natively, and the skill bridges messages to the Laravel endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;User → Telegram → OpenClaw Gateway → Skill &lt;span class="o"&gt;(&lt;/span&gt;SKILL.md&lt;span class="o"&gt;)&lt;/span&gt;
  → curl POST http://localhost:8000/creem-agent/chat
  → Response relayed back through OpenClaw → Telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The skill is a single &lt;code&gt;SKILL.md&lt;/code&gt; file — no shell scripts, no binaries. Install it from ClawHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw skills &lt;span class="nb"&gt;install &lt;/span&gt;openclaw-laravel-creem-agent-skill
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Support Both?
&lt;/h3&gt;

&lt;p&gt;Because OpenClaw adds value beyond basic messaging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-skill orchestration&lt;/strong&gt; — the agent becomes one skill among many&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session management&lt;/strong&gt; — OpenClaw handles conversation state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gateway security&lt;/strong&gt; — shared secrets, pairing approval&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel flexibility&lt;/strong&gt; — same skill works on Telegram, WebChat, Discord&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But if you don't use OpenClaw, the agent works perfectly fine on its own. No vendor lock-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI Package
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;laravel-creem-cli&lt;/code&gt; wraps the Creem API into Artisan commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan creem:subscriptions list &lt;span class="nt"&gt;--profile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default &lt;span class="nt"&gt;--json&lt;/span&gt;
php artisan creem:transactions list &lt;span class="nt"&gt;--json&lt;/span&gt;
php artisan creem:customers list &lt;span class="nt"&gt;--json&lt;/span&gt;
php artisan creem:products list &lt;span class="nt"&gt;--json&lt;/span&gt;
php artisan creem:whoami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's designed as a standalone package. If the native &lt;code&gt;creem&lt;/code&gt; CLI binary is installed, the agent uses it for speed. If not, &lt;code&gt;laravel-creem-cli&lt;/code&gt; handles everything through the SDK.&lt;/p&gt;

&lt;p&gt;This dual-driver architecture means zero configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent needs subscription data
  ↓
CreemCliManager:
  1. Check: is native `creem` binary available? (cached 24h)
  2. If yes → NativeCliDriver: shell exec `creem subscriptions list --json`
  3. If no  → ArtisanCliDriver: Creem::profile()-&amp;gt;subscriptions()-&amp;gt;list()
  ↓
Same JSON result either way
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Docker Topology
&lt;/h2&gt;

&lt;p&gt;The demo stack runs five services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="c1"&gt;# Laravel Octane + FrankenPHP (port 8000)&lt;/span&gt;
  &lt;span class="na"&gt;queue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# Queue worker for async jobs&lt;/span&gt;
  &lt;span class="na"&gt;scheduler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Cron runner for heartbeat schedule&lt;/span&gt;
  &lt;span class="na"&gt;simulator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Mock Creem API (internal only)&lt;/span&gt;
  &lt;span class="na"&gt;ngrok&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="c1"&gt;# Public tunnel for webhooks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkru9iqfo4fs9vwhh3o8p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkru9iqfo4fs9vwhh3o8p.png" alt="Docker: all five containers running — app, queue, scheduler, simulator, ngrok" width="800" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The simulator is only accessible inside the Docker network — it's not exposed to the host. The app talks to it via the Docker service name (&lt;code&gt;http://simulator/api/v1&lt;/code&gt;), and the same app config seamlessly switches to &lt;code&gt;https://api.creem.io/v1&lt;/code&gt; in production by changing one environment variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes This Different
&lt;/h2&gt;

&lt;p&gt;This isn't a script that calls an API and prints results. It's a &lt;strong&gt;platform&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Modular by design&lt;/strong&gt; — use any package independently or compose them together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-tier AI&lt;/strong&gt; — rule parser handles 80% of queries at zero cost; LLM catches everything else&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive, not reactive&lt;/strong&gt; — heartbeat detects problems before users report them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simulator-first development&lt;/strong&gt; — deterministic testing without real payment infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-store native&lt;/strong&gt; — manage multiple stores with independent configs and notification channels&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channel-agnostic&lt;/strong&gt; — Telegram, OpenClaw, Slack, HTTP, CLI — same agent, same logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-grade webhooks&lt;/strong&gt; — HMAC signature verification, retry handling, event-driven architecture&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Screenshot Reference
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8rywi1anjiwm2nkfrah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh8rywi1anjiwm2nkfrah.png" alt="Figure 1: System architecture" width="800" height="600"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 1:&lt;/strong&gt; Full system architecture — all packages, data flows, and integration points.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65g0x53d0i2mfpdf6mfz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F65g0x53d0i2mfpdf6mfz.png" alt="Figure 2: Simulator seeding" width="800" height="487"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 2:&lt;/strong&gt; Creem Simulator deterministic seeding. Parameters control exact data volumes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2z8587ly0j8mmpv61b6k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2z8587ly0j8mmpv61b6k.png" alt="Figure 3: Simulator advance with webhooks" width="800" height="298"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 3:&lt;/strong&gt; Scenario advancement with &lt;code&gt;--send-webhooks&lt;/code&gt;. Signed webhook delivered and processed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim3mojkt0neknth914fc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fim3mojkt0neknth914fc.png" alt="Figure 4: Telegram — first sale alert" width="800" height="298"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 4:&lt;/strong&gt; Real-time Telegram alert triggered by the simulator webhook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3yayyvpx2zie678foaxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3yayyvpx2zie678foaxl.png" alt="Figure 5: Multiple events — sales, cancellations, past-due" width="800" height="548"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 5:&lt;/strong&gt; Multiple advance commands generating sales, cancellations, and past-due transitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzselmu8xidloyyjrm23l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzselmu8xidloyyjrm23l.png" alt="Figure 6: Telegram — batch of alerts" width="800" height="589"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 6:&lt;/strong&gt; Telegram receiving 3 new sale alerts and 2 subscription cancellation alerts in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flat5lc40ivqccgq16u53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flat5lc40ivqccgq16u53.png" alt="Figure 7: curl requests" width="800" height="189"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 7:&lt;/strong&gt; Querying the agent via curl — status overview and payment issue check.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fon4uy1u5gqqed3tgh1q5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fon4uy1u5gqqed3tgh1q5.png" alt="Figure 8: Telegram — natural language queries" width="800" height="347"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 8:&lt;/strong&gt; Natural-language conversation in Telegram. The agent understands "how's the store doing?" and "are there any issues?" — same flow for direct webhook or OpenClaw path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa327piw19v4nk7wi2xa7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa327piw19v4nk7wi2xa7.png" alt="Figure 9: Docker topology" width="800" height="319"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 9:&lt;/strong&gt; Docker Compose topology — five services running: app, queue, scheduler, simulator, ngrok.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd3guy47bcnmr7ogq5sc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjd3guy47bcnmr7ogq5sc.png" alt="Figure 10: Heartbeat via Telegram" width="800" height="319"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Figure 10:&lt;/strong&gt; Heartbeat triggered via natural language in Telegram — "run it again" produces a full change report with new sales and customer counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The full source is on GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/romansh/laravel-creem" rel="noopener noreferrer"&gt;laravel-creem&lt;/a&gt; — SDK&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/romansh/laravel-creem-agent" rel="noopener noreferrer"&gt;laravel-creem-agent&lt;/a&gt; — Agent&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/romansh/laravel-creem-agent-demo" rel="noopener noreferrer"&gt;laravel-creem-agent-demo&lt;/a&gt; — Demo + Simulator&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/romansh/laravel-creem-cli" rel="noopener noreferrer"&gt;laravel-creem-cli&lt;/a&gt; — CLI&lt;/li&gt;
&lt;li&gt;&lt;a href="https://clawhub.com/skills/openclaw-laravel-creem-agent-skill" rel="noopener noreferrer"&gt;OpenClaw Skill on ClawHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The simulator means you can test the entire system locally without any Creem API keys. Seed data, advance the scenario, watch the agent react.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>webdev</category>
      <category>saas</category>
      <category>php</category>
    </item>
    <item>
      <title>LocalHelp: A Map Where Neighbors Help Each Other in One Click [DEV Weekend Challenge: Community]</title>
      <dc:creator>Roman Shalabanov</dc:creator>
      <pubDate>Mon, 02 Mar 2026 01:17:22 +0000</pubDate>
      <link>https://dev.to/roman_shalabanov_e53b30b6/localhelp-a-map-where-neighbors-help-each-other-in-one-click-dev-weekend-challenge-community-9in</link>
      <guid>https://dev.to/roman_shalabanov_e53b30b6/localhelp-a-map-where-neighbors-help-each-other-in-one-click-dev-weekend-challenge-community-9in</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;I'm from Ukraine. Over here, volunteering and mutual aid are not buzzwords. They are survival skills.&lt;/p&gt;

&lt;p&gt;Since 2022, our communities have learned to self-organize at a speed that surprises even ourselves. When pharmacies run out of ibuprofen, a neighbor finds it. When an elderly person needs a ride to the hospital, someone nearby drives them. When the power is out and you need a phone charger, you post in a group chat and three people respond in minutes.&lt;/p&gt;

&lt;p&gt;This happens every day. But it happens through messy group chats, lost messages, and zero structure. Requests get buried. People who want to help never see the ones who need it.&lt;/p&gt;

&lt;p&gt;LocalHelp is my attempt to fix that: &lt;strong&gt;a map where you post what you need, and the closest person who can help sees it first.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;LocalHelp&lt;/strong&gt; is a real-time, map-based micro-volunteering platform. The idea is dead simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You see a map of your neighborhood&lt;/li&gt;
&lt;li&gt;You pin a request for help (groceries, medicine, transport, anything)&lt;/li&gt;
&lt;li&gt;A neighbor sees it, clicks "I'll help", and both of you instantly get each other's contact&lt;/li&gt;
&lt;li&gt;When the help arrives, you close the request&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No middleman. No coordination overhead. No app store. Just a browser and a map.&lt;/p&gt;

&lt;p&gt;The whole interaction takes about 15 seconds from "I need help" to "someone is on the way."&lt;/p&gt;

&lt;h3&gt;
  
  
  The map tells you everything at a glance
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds8wj0jk49m0yg5nsiyi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fds8wj0jk49m0yg5nsiyi.png" alt="Map overview with colored markers and popup"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every category has its own color: blue for groceries, red for medicine, purple for transport, teal for everything else. You see the whole picture without clicking a single marker. Filter by category, draw a custom area on the map, and the list updates in real time.&lt;/p&gt;

&lt;p&gt;Tap any marker to see the full details, contact info, and a single "I'll help" button.&lt;/p&gt;

&lt;h3&gt;
  
  
  Once a neighbor takes your request, you know immediately
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl35umc561le39f47axn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl35umc561le39f47axn9.png" alt="Owner view with helper info and visual markers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No page reload. No email notification to check later. The marker turns orange the moment someone takes it, and the helper's name and contact appear right in the popup. WebSockets make this instant for everyone on the map.&lt;/p&gt;

&lt;p&gt;Your own requests have a white center dot so you can always spot them. The "Mark done" button closes the loop when help has arrived.&lt;/p&gt;

&lt;h3&gt;
  
  
  Click any marker for full details
&lt;/h3&gt;

&lt;p&gt;Tap a marker to open a popup with everything you need: description, category, contact info, and deadline. If it is open, you will see a big "I'll help" button. If someone already took it, you will see who is helping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8huedspmxdwtvzrd4nyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8huedspmxdwtvzrd4nyb.png" alt="Marker popup — open request with I'll help button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fth80sbs5yrogwiw0o4ir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fth80sbs5yrogwiw0o4ir.png" alt="Marker popup — taken request with helper info"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Track everything you asked for in one place
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flggqygn8ccus44mfsjy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flggqygn8ccus44mfsjy9.png" alt="My Needs modal showing in-progress and fulfilled requests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "My Needs" panel shows all your active requests: who took them, their contact info, deadline, and full history. Fulfilled requests stay visible until they expire, so nothing disappears when you are not looking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manage what you are helping with
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbk0244w77nhe7bc3pjln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbk0244w77nhe7bc3pjln.png" alt="My Help modal showing assigned tasks with deadlines"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "My Help" panel is your volunteer dashboard. Every task you committed to, with the requester's contact, category, and hard deadline. Changed your mind? Hit "Give up" and the request goes back to the map as open. No guilt, no friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/1Y72TaLeX1Y"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;Here is how to run it yourself in under 2 minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/romansh/localhelp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;localhelp
composer &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install
cp&lt;/span&gt; .env.example .env &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php artisan key:generate
&lt;span class="nb"&gt;touch &lt;/span&gt;database/database.sqlite &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; php artisan migrate &lt;span class="nt"&gt;--seed&lt;/span&gt;
npm run build
php artisan serve          &lt;span class="c"&gt;# App&lt;/span&gt;
php artisan reverb:start   &lt;span class="c"&gt;# WebSocket server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Docker with Traefik + Cloudflare Tunnel is also supported:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/romansh" rel="noopener noreferrer"&gt;
        romansh
      &lt;/a&gt; / &lt;a href="https://github.com/romansh/localhelp" rel="noopener noreferrer"&gt;
        localhelp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      LocalHelp: find or offer help in your neighborhood with real-time map notifications.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;LocalHelp&lt;/h1&gt;
&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Neighbors helping neighbors — one click on the map.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LocalHelp is a real-time, map-based micro-volunteering platform. Anyone can pin a request for help on the map — groceries, medicine, transport, anything. A neighbor sees it, clicks "I'll help", and both sides instantly get each other's contact. No middleman, no coordination overhead.&lt;/p&gt;

&lt;p&gt;Built for the &lt;strong&gt;DEV Weekend Challenge&lt;/strong&gt; in 48 hours.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How It Works&lt;/h2&gt;
&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;1. Browse requests near you&lt;/h3&gt;
&lt;/div&gt;

&lt;p&gt;Color-coded markers make the whole city readable at a glance — blue for food, red for medicine, purple for transport, teal for other. Filter by category, draw a custom area on the map, and only see what's relevant to you.&lt;/p&gt;

&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/romansh/localhelp/docs/images/Localhelp-1.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fromansh%2Flocalhelp%2Fdocs%2Fimages%2FLocalhelp-1.png" alt="Map overview with popup"&gt;&lt;/a&gt;
&lt;em&gt;Tap any marker to see the full request, contact details, and a single "I'll help" button&lt;/em&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;2. Take a request — your neighbors see it instantly&lt;/h3&gt;

&lt;/div&gt;
&lt;p&gt;When you click "I'll help", the marker gets an &lt;strong&gt;orange ring&lt;/strong&gt; and a status dot…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/romansh/localhelp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;Key parts of the codebase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Livewire v4 components&lt;/strong&gt; handle all interactivity without writing a single REST endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Laravel Reverb&lt;/strong&gt; (WebSockets) pushes marker updates to every connected client in real time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaflet.js + Leaflet Draw&lt;/strong&gt; power the map, custom markers, area selection, and popups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google OAuth&lt;/strong&gt; for one-click login (no passwords to manage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-spam&lt;/strong&gt;: reCAPTCHA, daily rate limits, keyword blacklist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-expiration&lt;/strong&gt;: requests disappear after 1h to 7 days (user's choice)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt;: zero infrastructure, works out of the box&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Stack:&lt;/strong&gt; Laravel 12, Livewire v4, Alpine.js, TailwindCSS v4, Leaflet.js, Laravel Reverb, SQLite&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Friday + Saturday&lt;/strong&gt; - thinking, sketching, choosing between ideas. My first concept was actually a tournament scheduling app for a local table tennis league I play in. It would account for power outages and air raid alerts when rescheduling matches. That is a real problem where I live. It would have been a fun build, but the audience is tiny and the potential stops at one league. Then I thought: what if I build something that any neighborhood can use, and that can later be narrowed down to any niche? That is how LocalHelp was born.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunday&lt;/strong&gt; - all the coding. Database schema, Google OAuth, Leaflet map, markers, popups, CRUD, real-time broadcasting, category filters, area selection, helper workflow, My Needs / My Help panels, visual marker system, Ukrainian translation, spam protection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sunday night into Monday&lt;/strong&gt; - Docker setup, polish, screenshots, this post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Design decisions that mattered:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Map-first, not list-first.&lt;/strong&gt; When you need help from a neighbor, distance is the most important filter. A list cannot show you that. A map does it instantly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No chat, no comments, no threads.&lt;/strong&gt; The app gives you a phone number or Telegram handle. You call. You text. The real conversation happens where people already communicate. The app just connects you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual status on markers.&lt;/strong&gt; You should never have to click a marker to know if a request is taken or fulfilled. Orange ring = someone is helping. Faded = done. White dot = yours. This was inspired by how traffic lights work: color carries meaning before you read anything.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SQLite by default.&lt;/strong&gt; This is a neighborhood tool. It does not need Postgres until it serves 10,000 people. Zero-config setup means anyone can fork it and run it for their block in under 2 minutes.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why this matters beyond a hackathon:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LocalHelp is intentionally generic. It could be the starting point for something more specific: a tool for a running club to coordinate rides to races, a neighborhood watch reporting system, a mutual aid network for a refugee community, or a disaster response coordination board. The map-first, real-time, one-click-to-connect pattern works for any community where proximity matters. Fork it, change the categories, and you have a boilerplate for your own niche.&lt;/p&gt;

&lt;p&gt;In Ukraine, we learned that the best systems are the ones people actually use under stress. They need to be fast, obvious, and forgiving. That is what I tried to build this weekend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A note on AI:&lt;/strong&gt; I used GitHub Copilot Chat in VS Code. Maybe a little. Maybe not so little. But here is the thing: Copilot does not know what your community needs. It does not know that orange means "taken" and gray means "done" in your head. It does not know that the popup should show a phone number, not an email. AI is a power tool, but you still need to know what you are building and why. The intent, the UX decisions, the architecture, the flow from "I need help" to "someone is coming" - that is all human.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with care from Ukraine.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building a Laravel SDK for Creem.io: multi-profile billing, webhook events, and an interactive demo</title>
      <dc:creator>Roman Shalabanov</dc:creator>
      <pubDate>Fri, 27 Feb 2026 11:55:47 +0000</pubDate>
      <link>https://dev.to/roman_shalabanov_e53b30b6/building-a-laravel-sdk-for-creemio-multi-profile-billing-webhook-events-and-an-interactive-demo-30ed</link>
      <guid>https://dev.to/roman_shalabanov_e53b30b6/building-a-laravel-sdk-for-creemio-multi-profile-billing-webhook-events-and-an-interactive-demo-30ed</guid>
      <description>&lt;p&gt;I recently open-sourced a Laravel SDK for &lt;a href="https://creem.io" rel="noopener noreferrer"&gt;Creem.io&lt;/a&gt; and wanted to write up the story behind it, because the path to building it was a bit roundabout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backstory
&lt;/h2&gt;

&lt;p&gt;My existing project uses &lt;a href="https://github.com/thephpleague/omnipay" rel="noopener noreferrer"&gt;Omnipay&lt;/a&gt;, the PHP League's payment abstraction library (not a payment provider itself), to handle checkout through multiple gateways via a single interface. I originally planned to stick with a provider that already had an Omnipay driver. But mid-integration I switched to Creem. Since the project was already wired through Omnipay, I wrote a driver for it: &lt;strong&gt;&lt;a href="https://github.com/romansh/omnipay-creem" rel="noopener noreferrer"&gt;romansh/omnipay-creem&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Omnipay is a solid choice when you need to swap gateways with one line of code. The trade-off is that it's a lowest-common-denominator abstraction: you get &lt;code&gt;purchase()&lt;/code&gt; and &lt;code&gt;completePurchase()&lt;/code&gt;, and everything else (webhook routing, event dispatching, config management, retry logic) you have to build yourself.&lt;/p&gt;

&lt;p&gt;At some point I discovered Creem had a developer bounty for an official Laravel SDK. Since I was already working with their API and had a feel for what was missing, I decided to build it properly: a Laravel-native package that handles all that boilerplate out of the box. If a package like this had existed when I started, I probably would have used it instead of writing the Omnipay driver.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; this article is also part of that bounty. That said, the packages fill a real gap and I would have written this up regardless.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem with typical payment integrations
&lt;/h2&gt;

&lt;p&gt;Most payment integrations are built around one API key per app. That works until you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenancy&lt;/strong&gt;: each tenant with their own billing account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple storefronts&lt;/strong&gt;: different products or brands on separate Creem accounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staging vs production&lt;/strong&gt;: without touching &lt;code&gt;.env&lt;/code&gt; per environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Departmental billing&lt;/strong&gt;: isolated billing within the same app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/romansh/laravel-creem" rel="noopener noreferrer"&gt;romansh/laravel-creem&lt;/a&gt;&lt;/strong&gt; is a full-featured SDK with Laravel-native patterns.&lt;/p&gt;

&lt;p&gt;What's inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complete API coverage&lt;/strong&gt;: Products, Checkouts, Customers, Subscriptions, Transactions, Licenses, Discount Codes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-profile config&lt;/strong&gt;: switch API keys per request with &lt;code&gt;Creem::profile('store_b')&lt;/code&gt; or override inline with &lt;code&gt;Creem::withConfig([...])&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks done right&lt;/strong&gt;: auto-registered routes, HMAC signature verification, typed Laravel events including &lt;code&gt;GrantAccess&lt;/code&gt; / &lt;code&gt;RevokeAccess&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artisan tooling&lt;/strong&gt;: &lt;code&gt;php artisan creem:test-webhook checkout.completed&lt;/code&gt; for local testing without hitting the real API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Laravel 10 / 11 / 12, PHP 8.1-8.4&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Well tested&lt;/strong&gt;: unit + feature tests, PSR-12, full PHPDoc&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a checkout
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Romansh\LaravelCreem\Facades\Creem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Creem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;checkouts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'product_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'prod_123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'success_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dashboard'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'customer'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$checkout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'checkout_url'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-profile in action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use a different Creem account per product line&lt;/span&gt;
&lt;span class="nv"&gt;$txns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Creem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Webhook listener: grant access on payment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GrantAccess&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;GrantAccess&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'plan'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pro'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'subscribed_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require romansh/laravel-creem
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creem-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;CREEM_API_KEY&lt;/code&gt; and &lt;code&gt;CREEM_WEBHOOK_SECRET&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and you're good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive demo app
&lt;/h2&gt;

&lt;p&gt;There's also &lt;strong&gt;&lt;a href="https://github.com/romansh/laravel-creem-demo" rel="noopener noreferrer"&gt;romansh/laravel-creem-demo&lt;/a&gt;&lt;/strong&gt;, a full working app (Laravel 12 + Livewire + Tailwind) built to explore every feature through a web UI: configure API keys in the browser, create products, trigger checkouts, manage subscriptions, and watch webhook events arrive in real time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project romansh/laravel-creem-demo my-creem-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-creem-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker setup includes an optional Cloudflare Tunnel so webhooks work locally without any port forwarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;API Setup: credentials, webhook URL, profile tabs:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxefzoeu24nijwu7dj6fq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxefzoeu24nijwu7dj6fq.png" alt="API Setup: credentials, webhook URL, profile tabs" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Webhooks and Access: live event log, GrantAccess/RevokeAccess in real time:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o1woo2dwi34kdmr2b4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o1woo2dwi34kdmr2b4h.png" alt="Webhooks and Access: live event log, GrantAccess/RevokeAccess in real time" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transactions: payment history with amounts and customer emails:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytfmugnyhusw17j5vp7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytfmugnyhusw17j5vp7d.png" alt="Transactions: payment history with amounts and customer emails" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Discounts: create percentage/fixed discount codes:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gzekm6vzr6kc6cul760.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gzekm6vzr6kc6cul760.png" alt="Discounts: create percentage/fixed discount codes" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Subscriptions: recurring plans with billing periods:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aqe5fefcfzo9j4e46dq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aqe5fefcfzo9j4e46dq.png" alt="Subscriptions: recurring plans with billing periods" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One-Time Payments: product preview modal, checkout flow:&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrk3modysi85xsq09b4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrk3modysi85xsq09b4d.png" alt="One-Time Payments: product preview modal, checkout flow" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Laravel SDK:&lt;/strong&gt; &lt;a href="https://github.com/romansh/laravel-creem" rel="noopener noreferrer"&gt;github.com/romansh/laravel-creem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo app:&lt;/strong&gt; &lt;a href="https://github.com/romansh/laravel-creem-demo" rel="noopener noreferrer"&gt;github.com/romansh/laravel-creem-demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omnipay driver:&lt;/strong&gt; &lt;a href="https://github.com/romansh/omnipay-creem" rel="noopener noreferrer"&gt;github.com/romansh/omnipay-creem&lt;/a&gt; (framework-agnostic, for projects already using Omnipay)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback and issues are welcome on GitHub or in the comments below. If you end up using one of these packages, I'd be happy to hear how it works out.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Laravel SDK for Creem.io: multi-profile billing, webhook events, and an interactive demo</title>
      <dc:creator>Roman Shalabanov</dc:creator>
      <pubDate>Fri, 27 Feb 2026 11:55:47 +0000</pubDate>
      <link>https://dev.to/roman_shalabanov_e53b30b6/building-a-laravel-sdk-for-creemio-multi-profile-billing-webhook-events-and-an-interactive-demo-25hg</link>
      <guid>https://dev.to/roman_shalabanov_e53b30b6/building-a-laravel-sdk-for-creemio-multi-profile-billing-webhook-events-and-an-interactive-demo-25hg</guid>
      <description>&lt;p&gt;I recently open-sourced a Laravel SDK for &lt;a href="https://creem.io" rel="noopener noreferrer"&gt;Creem.io&lt;/a&gt; and wanted to write up the story behind it, because the path to building it was a bit roundabout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backstory
&lt;/h2&gt;

&lt;p&gt;My existing project uses &lt;a href="https://github.com/thephpleague/omnipay" rel="noopener noreferrer"&gt;Omnipay&lt;/a&gt;, the PHP League's payment abstraction library (not a payment provider itself), to handle checkout through multiple gateways via a single interface. I originally planned to stick with a provider that already had an Omnipay driver. But mid-integration I switched to Creem. Since the project was already wired through Omnipay, I wrote a driver for it: &lt;strong&gt;&lt;a href="https://github.com/romansh/omnipay-creem" rel="noopener noreferrer"&gt;romansh/omnipay-creem&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Omnipay is a solid choice when you need to swap gateways with one line of code. The trade-off is that it's a lowest-common-denominator abstraction: you get &lt;code&gt;purchase()&lt;/code&gt; and &lt;code&gt;completePurchase()&lt;/code&gt;, and everything else (webhook routing, event dispatching, config management, retry logic) you have to build yourself.&lt;/p&gt;

&lt;p&gt;At some point I discovered Creem had a developer bounty for an official Laravel SDK. Since I was already working with their API and had a feel for what was missing, I decided to build it properly: a Laravel-native package that handles all that boilerplate out of the box. If a package like this had existed when I started, I probably would have used it instead of writing the Omnipay driver.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; this article is also part of that bounty. That said, the packages fill a real gap and I would have written this up regardless.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem with typical payment integrations
&lt;/h2&gt;

&lt;p&gt;Most payment integrations are built around one API key per app. That works until you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenancy&lt;/strong&gt;: each tenant with their own billing account&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple storefronts&lt;/strong&gt;: different products or brands on separate Creem accounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Staging vs production&lt;/strong&gt;: without touching &lt;code&gt;.env&lt;/code&gt; per environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Departmental billing&lt;/strong&gt;: isolated billing within the same app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/romansh/laravel-creem" rel="noopener noreferrer"&gt;romansh/laravel-creem&lt;/a&gt;&lt;/strong&gt; is a full-featured SDK with Laravel-native patterns.&lt;/p&gt;

&lt;p&gt;What's inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complete API coverage&lt;/strong&gt;: Products, Checkouts, Customers, Subscriptions, Transactions, Licenses, Discount Codes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-profile config&lt;/strong&gt;: switch API keys per request with &lt;code&gt;Creem::profile('store_b')&lt;/code&gt; or override inline with &lt;code&gt;Creem::withConfig([...])&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks done right&lt;/strong&gt;: auto-registered routes, HMAC signature verification, typed Laravel events including &lt;code&gt;GrantAccess&lt;/code&gt; / &lt;code&gt;RevokeAccess&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artisan tooling&lt;/strong&gt;: &lt;code&gt;php artisan creem:test-webhook checkout.completed&lt;/code&gt; for local testing without hitting the real API&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Laravel 10 / 11 / 12, PHP 8.1-8.4&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Well tested&lt;/strong&gt;: unit + feature tests, PSR-12, full PHPDoc&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a checkout
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Romansh\LaravelCreem\Facades\Creem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$checkout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Creem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;checkouts&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="s1"&gt;'product_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'prod_123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'success_url'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dashboard'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;'customer'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$checkout&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'checkout_url'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-profile in action
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use a different Creem account per product line&lt;/span&gt;
&lt;span class="nv"&gt;$txns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Creem&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'product_a'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Webhook listener: grant access on payment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GrantAccess&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;GrantAccess&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'email'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;?-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'plan'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'pro'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'subscribed_at'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()]);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Quick start
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require romansh/laravel-creem
php artisan vendor:publish &lt;span class="nt"&gt;--tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creem-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;CREEM_API_KEY&lt;/code&gt; and &lt;code&gt;CREEM_WEBHOOK_SECRET&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt; and you're good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive demo app
&lt;/h2&gt;

&lt;p&gt;There's also &lt;strong&gt;&lt;a href="https://github.com/romansh/laravel-creem-demo" rel="noopener noreferrer"&gt;romansh/laravel-creem-demo&lt;/a&gt;&lt;/strong&gt;, a full working app (Laravel 12 + Livewire + Tailwind) built to explore every feature through a web UI: configure API keys in the browser, create products, trigger checkouts, manage subscriptions, and watch webhook events arrive in real time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer create-project romansh/laravel-creem-demo my-creem-app
&lt;span class="nb"&gt;cd &lt;/span&gt;my-creem-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker setup includes an optional Cloudflare Tunnel so webhooks work locally without any port forwarding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxefzoeu24nijwu7dj6fq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxefzoeu24nijwu7dj6fq.png" alt="API Setup: credentials, webhook URL, profile tabs" width="800" height="372"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;API Setup: credentials, webhook URL, profile tabs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrk3modysi85xsq09b4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyrk3modysi85xsq09b4d.png" alt="Webhooks and Access: live event log, GrantAccess/RevokeAccess in real time" width="800" height="372"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Webhooks and Access: live event log, GrantAccess/RevokeAccess in real time&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aqe5fefcfzo9j4e46dq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6aqe5fefcfzo9j4e46dq.png" alt="Transactions: payment history with amounts and customer emails" width="800" height="372"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Transactions: payment history with amounts and customer emails&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gzekm6vzr6kc6cul760.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7gzekm6vzr6kc6cul760.png" alt="Discounts: create percentage/fixed discount codes" width="800" height="372"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Discounts: create percentage/fixed discount codes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytfmugnyhusw17j5vp7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fytfmugnyhusw17j5vp7d.png" alt="Subscriptions: recurring plans with billing periods" width="800" height="372"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Subscriptions: recurring plans with billing periods&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o1woo2dwi34kdmr2b4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4o1woo2dwi34kdmr2b4h.png" alt="One-Time Payments: product preview modal, checkout flow" width="800" height="372"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;One-Time Payments: product preview modal, checkout flow&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Laravel SDK:&lt;/strong&gt; &lt;a href="https://github.com/romansh/laravel-creem" rel="noopener noreferrer"&gt;github.com/romansh/laravel-creem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo app:&lt;/strong&gt; &lt;a href="https://github.com/romansh/laravel-creem-demo" rel="noopener noreferrer"&gt;github.com/romansh/laravel-creem-demo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Omnipay driver:&lt;/strong&gt; &lt;a href="https://github.com/romansh/omnipay-creem" rel="noopener noreferrer"&gt;github.com/romansh/omnipay-creem&lt;/a&gt; (framework-agnostic, for projects already using Omnipay)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feedback and issues are welcome on GitHub or in the comments below. If you end up using one of these packages, I'd be happy to hear how it works out.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
