Developer API · v1

Build checkouts that pay straight into your own Stripe.

Create a Chargedeck checkout for any dynamic cart total, redirect the buyer, and get a signed webhook the moment they pay. Money settles to your connected processor - Chargedeck just records the order.

Introduction

The Chargedeck Integration API lets any external site - a WooCommerce store, a custom checkout, any platform - spin up a hosted Chargedeck checkout for a dynamic cart total. The buyer pays on a branded Chargedeck page, the order and total land in Chargedeck, and your site is notified with a signed webhook so it can mark its own order paid.

Non-custodial by design. An API-created checkout charges the buyer's card straight to your own connected processor - Stripe Connect, PayPal, or Square. Money never touches a Chargedeck account. Chargedeck records the order and the total and holds nothing.

Base URL

Base URL
https://checkout.2.25.199.135.nip.io

Amounts

Every amount is an integer in minor units, exactly like Stripe: 4999 means £49.99, 2500 means $25.00. The minimum is 30 (the processor floor). Supported currencies: gbp, usd, eur, cad, aud (always lowercase).

Authentication

Every API request authenticates with your secret API key as a Bearer token. Keys look like cdk_live_<hex> and resolve to exactly one seller - a key can only ever read or act on your own data.

HTTP header
Authorization: Bearer cdk_live_4f0b9c2e…
Where to get your key. Open your Chargedeck dashboard and go to the Developers section. Click Reveal key - the full cdk_live_ secret is shown once. Store it server-side.
Keep it secret. This key is server-side only - never ship it to a browser, a mobile app, or any buyer-facing response. Anyone with it can create checkouts on your account. Rotating the key from the dashboard instantly invalidates the old one (and any dynamic signed links built with it - see below).

Create a checkout

Create a hosted checkout for a dynamic cart total. The amount you send here is server-trusted - because it arrives over an authenticated Bearer call, it is frozen server-side and the buyer can never change it.

POST/api/v1/checkouts
cURL
curl https://checkout.2.25.199.135.nip.io/api/v1/checkouts \
  -H "Authorization: Bearer cdk_live_4f0b9c2e…" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4999,
    "currency": "gbp",
    "description": "Order #1042",
    "reference": "1042",
    "success_url": "https://siteA.com/thank-you?o=1042",
    "cancel_url": "https://siteA.com/cart",
    "customer_email": "buyer@example.com"
  }'
FieldTypeNotes
amountintegerRequired. Minor units, 3099999999. Authoritative - line items do not need to sum to it.
currencystringRequired. One of gbp usd eur cad aud.
referencestringYour external order id (≤200 chars). Echoed back in the webhook.
descriptionstringShown on the checkout page.
success_urlstringStored for your own routing (see the note in the redirect flow).
cancel_urlstringStored for your own routing.
customer_emailstringOptional pre-fill.
line_itemsarrayOptional, for your own records. Never sent to any processor (white-label).

Response

200 OK · application/json
{
  "id": "cs_7hK2p…",
  "url": "https://checkout.2.25.199.135.nip.io/p/cs_7hK2p…",
  "reference": "1042",
  "amount": 4999,
  "currency": "gbp",
  "status": "open",
  "expires_at": 1720986400
}

Redirect the buyer's browser to url. To poll a session's status later, call GET /api/v1/checkouts/:id with the returned id; to list orders, call GET /api/v1/orders?reference=1042.

The redirect flow

The whole handshake is four steps. The buyer never leaves a Chargedeck-hosted page until they are done, and the webhook is the authoritative "paid" signal your site acts on.

01
Create
Your server calls POST /api/v1/checkouts and gets back a hosted url.
02
Redirect
Send the buyer's browser to /p/cs_… - the branded Chargedeck checkout.
03
Pay
Buyer pays. The charge settles to your connected processor. Chargedeck records the order.
04
Webhook
Chargedeck POSTs a signed order.paid event to your webhook URL. You mark your order paid.
Don't trust the browser return for payment status. The hosted checkout shows its own success screen; it does not auto-redirect to your success_url. Treat the signed webhook as the only source of truth that an order is paid. success_url and cancel_url are stored for your own routing.

Verify webhooks

When an order becomes paid, Chargedeck POSTs a JSON order.paid event to the webhook URL you set in the dashboard. It is signed with your webhook secret (whsec_<hex>) so you can verify authenticity and reject replays.

Request headers Chargedeck sends
Chargedeck-Signature: t=1720900000,v1=4e0b…c7
Content-Type: application/json
User-Agent: Chargedeck-Webhooks/1
Event body
{
  "id": "evt_9c3f…",
  "type": "order.paid",
  "created": 1720900000,
  "data": {
    "order_id": "ord_a1b2…",
    "checkout_id": "cs_7hK2p…",
    "reference": "1042",
    "amount": 4999,
    "currency": "gbp",
    "status": "paid",
    "seller_id": "sel_44f0…",
    "paid_at": 1720900000
  }
}

How to verify

  1. Read the raw request body - the exact bytes, before any JSON parse.
  2. Parse t and v1 from the Chargedeck-Signature header.
  3. Reject if t is more than 300 seconds from now (replay window).
  4. Compute HMAC-SHA256(secret, "<t>.<rawBody>") and compare to v1 in constant time.
  5. Only then parse the JSON and mark your order paid (idempotently - the event may arrive more than once).

PHP

PHP
$secret = 'whsec_…';                          // from your dashboard
$raw    = file_get_contents('php://input');   // RAW body, before json_decode
$hdr    = $_SERVER['HTTP_CHARGEDECK_SIGNATURE'] ?? '';

// parse "t=…,v1=…"
$t = $v1 = '';
foreach (explode(',', $hdr) as $part) {
  [$k, $v] = array_pad(explode('=', $part, 2), 2, '');
  if ($k === 't')  $t  = $v;
  if ($k === 'v1') $v1 = $v;
}

// reject replays (300s window)
if (abs(time() - (int)$t) > 300) { http_response_code(400); exit; }

$expected = hash_hmac('sha256', $t . '.' . $raw, $secret);
if (!hash_equals($expected, $v1)) { http_response_code(401); exit; }

$event = json_decode($raw, true);
if (($event['type'] ?? '') === 'order.paid') {
  $d = $event['data'];
  // look up YOUR order by $d['reference']; check amount + currency; mark paid (idempotent)
}
http_response_code(200);

Node.js (Express)

Node.js
const crypto = require('crypto');
const SECRET = 'whsec_…'; // from your dashboard

// IMPORTANT: capture the RAW body, not the parsed object
app.post('/chargedeck/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const raw = req.body;                       // Buffer of exact bytes
    const hdr = req.headers['chargedeck-signature'] || '';
    const parts = Object.fromEntries(
      hdr.split(',').map(p => p.split('=')));
    const t = parts.t, v1 = parts.v1 || '';

    // reject replays (300s window)
    if (Math.abs(Date.now()/1000 - Number(t)) > 300)
      return res.status(400).end();

    const expected = crypto.createHmac('sha256', SECRET)
      .update(t + '.' + raw).digest('hex');
    const a = Buffer.from(expected), b = Buffer.from(v1);
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b))
      return res.status(401).end();

    const event = JSON.parse(raw.toString('utf8'));
    if (event.type === 'order.paid') {
      const d = event.data;
      // look up YOUR order by d.reference; verify d.amount + d.currency; mark paid (idempotent)
    }
    res.status(200).end();
  });
Also check the amount. After the signature passes, confirm data.amount and data.currency match what you expected for data.reference before fulfilling. Events are delivered at-least-once, so keep your "mark paid" step idempotent.

WooCommerce plugin

The Chargedeck gateway plugin wires all of the above into WooCommerce automatically: it creates a checkout per order, redirects the buyer, and completes the WC order from the signed webhook - no code required.

Install

  1. Download chargedeck-woocommerce.zip.
  2. In WordPress admin go to Plugins → Add New → Upload Plugin, choose the zip, and Activate.
  3. Go to WooCommerce → Settings → Payments and enable the Chargedeck gateway.
  4. In the gateway settings, paste your API secret (cdk_live_…) and your Webhook secret (whsec_…) - both from your Chargedeck dashboard's Developers section. Leave the API base URL as https://checkout.2.25.199.135.nip.io.
  5. Back in the Chargedeck dashboard, set your webhook URL to the plugin's receiver route:
    Webhook URL
    https://YOUR-STORE.com/wp-json/chargedeck/v1/webhook
How it settles orders. At checkout the plugin calls POST /api/v1/checkouts with the WooCommerce order total (in minor units) and its order id as reference, then redirects the buyer to the hosted page. When payment completes, Chargedeck's signed order.paid webhook - verified and amount-checked by the plugin - marks the WooCommerce order paid. The browser return is never trusted for payment status.

Sharing & Social

Everything below turns a Chargedeck link into something you can actually post: QR codes for print and stories, a link-in-bio storefront, rich unfurls when a link is pasted into a chat, ad-pixel + UTM attribution, one-tap Apple Pay / Google Pay, memorable short slugs, and a ManyChat "comment → DM" recipe. Same non-custodial guarantee applies throughout - none of these features ever holds funds; they only link to the checkouts money already settles through, or record attribution alongside an order.

What stays white-label vs. what's Chargedeck-branded. The buyer's checkout page (/p/<slug>) is always your brand - no "Powered by Chargedeck". Only your public storefront (/s/<handle>) and your dashboard carry Chargedeck branding, because that's your creator hub, not the buyer's pay page.

QR codes for any link

Chargedeck serves a live SVG QR code for any URL from a single endpoint - no client library, no image to host. Point data at the full URL you want people to scan.

GET/qr.svg?data=<url>&size=<px>
ParamTypeNotes
datastringRequired. The URL/text to encode, URL-encoded, ≤ 1024 chars. Missing or too long → 400.
sizeintegerOutput width in pixels. Default 256, clamped to [64, 1024].

Drop it straight into an <img> - the response is image/svg+xml and cached for a day, so it stays crisp at any print size:

HTML · embed & download
<!-- show it -->
<img src="/qr.svg?data=https%3A%2F%2Fcheckout.2.25.199.135.nip.io%2Fp%2Fsourdough&size=240"
     alt="Scan to pay" width="240" height="240">

<!-- let them download the vector -->
<a href="/qr.svg?data=https%3A%2F%2Fcheckout.2.25.199.135.nip.io%2Fp%2Fsourdough&size=512"
   download="sourdough-qr.svg">Download SVG</a>

In the dashboard: every Pages card, payment-link row, and your storefront gets a small QR button. Clicking it pops the code with a Download SVG link - print it on a flyer, a table tent, or a product tag and it scans straight to that checkout. Because the library renders vector paths from the text (never markup), the encoded URL can never inject anything; if the qrcode module is unavailable the endpoint simply returns 503 rather than crashing.

Link-in-bio storefront

A storefront is your one shareable link - https://checkout.2.25.199.135.nip.io/s/<handle> - a mobile-first, Chargedeck-branded page with your avatar, title, bio, accent colour, and an ordered stack of tappable buttons. Each button just links to one of your own checkouts (a /p/<slug> page or a /l/<id> payment link), so it holds no money and needs no new payment logic. Build and reorder it from the dashboard's Storefront section.

What it looks like: a single centred column, max ~560px wide, avatar and title up top, your bio beneath, then a vertical run of large rounded buttons - "Sourdough Course", "Tip jar", "1:1 Consult" - each opening its white-label checkout. A subtle "Made with Chargedeck" footer sits at the bottom (this surface is ours to brand).

Automation tools and your own site can read the public storefront as JSON. It never leaks your seller id, pixel ids, or any secret:

GET/api/public/storefront/:handle
200 OK · application/json
{
  "storefront": {
    "handle": "jane-makes",
    "title": "Jane Makes",
    "bio": "Ceramics & workshops",
    "avatar": "https://…/avatar.jpg",
    "shareImage": "https://…/og.jpg",
    "accent": "#5D4BF6",
    "theme": "aurora",
    "items": [
      { "label": "Sourdough Course", "url": "/p/sourdough-a1b2", "kind": "page" },
      { "label": "Tip jar",          "url": "/l/plink_9f3c",  "kind": "link" }
    ],
    "brand": "Chargedeck"
  }
}
# 404 when the handle is missing or unpublished
Non-custodial. Storefront items are just links to checkouts you already own. Chargedeck never becomes a payee, never nets balances - every button still settles straight to your own Stripe / PayPal / Square.

Open Graph rich previews

When someone pastes a Chargedeck link into iMessage, WhatsApp, Slack, Discord, X/Twitter, Facebook, LinkedIn or Telegram, that platform's scraper fetches the page to build a preview card. Those scrapers do not run JavaScript - so Chargedeck injects the og: and twitter: meta tags into the served HTML <head> at request time, server-side. No config needed; it just works on:

  • /p/:slug - product / checkout preview (from the page + its seller).
  • /s/:handle - storefront preview.
  • /l/:linkId - payment links unfurl for bots too; humans still get the normal redirect.

The always-present tags are og:title, og:description, og:url, og:type, plus twitter:card / twitter:title / twitter:description. Every value is HTML-escaped, and the block is inserted before </head> - existing fonts, styles and the SPA script are never removed. Here's roughly what a checkout page emits:

Injected into <head> (server-side)
<!-- og:start -->
<meta property="og:title"       content="Sourdough Course - Jane Makes">
<meta property="og:description" content="6 weeks, live, small groups.">
<meta property="og:url"         content="https://checkout.2.25.199.135.nip.io/p/sourdough-a1b2">
<meta property="og:type"        content="product">
<meta name="twitter:card"     content="summary_large_image">
<meta property="og:image"       content="https://…/course.jpg">
<!-- og:end -->

Setting the share image. The og:image is resolved by a strict priority chain - the first http(s) image wins, otherwise og:image is simply omitted (Chargedeck never invents a broken URL, and a bare card still renders as twitter:card=summary):

/p/
Checkout page
page Share image → page image → page logo → seller logo → omit.
·
/s/
Storefront
storefront Share image → avatar → omit.

So to control the unfurl, set a Share image on the page (or storefront) in the dashboard - paste an https:// image URL (ideally ~1200×630). That field overrides everything below it in the chain.

Meta / TikTok pixels + UTM tracking

Attach your own ad pixels so conversions land in Meta Ads Manager and TikTok Ads. You configure the IDs once in the dashboard's Sharing section; Chargedeck injects the official base snippet server-side into /p/:slug and /s/:handle, and fires the purchase events client-side at the right moments.

POST/api/seller/pixels
Body · save your pixel IDs
{ "metaPixelId": "1234567890", "tiktokPixelId": "C9AB12KD34" }
# empty string clears a pixel; current values also appear in GET /api/settings as `social`
Strict validation - IDs are rejected, never sanitized. A Meta Pixel ID must be 6–20 digits (/^\d{6,20}$/); a TikTok Pixel ID must be 6–32 alphanumerics (/^[A-Za-z0-9]{6,32}$/). Anything else returns 400. Only a validated ID is ever interpolated into a script tag, so a pixel field can never be a script-injection vector.

Which events fire where:

EventWhereWhen
PageViewserver-side (in <head>)Whenever the checkout/storefront page loads - works even without JS.
InitiateCheckoutclient-sideAt the start of a real payment attempt (Stripe / Square pay, PayPal createOrder).
Purchase / CompletePaymentclient-sideOn the success screen, once, for every provider.

Client events go through optional chaining (window.fbq?.(…) / window.ttq?.(…)), so a page with no pixel configured is a silent no-op, and preview/demo traffic is skipped so it never pollutes your real pixel.

UTM attribution. Append the usual query params to any link you share and Chargedeck records them on the resulting order - so you can see which platform actually drove the sale, not just clicks:

Tag your shared links
https://checkout.2.25.199.135.nip.io/p/sourdough?utm_source=instagram&utm_medium=bio&utm_campaign=launch
# utm_source · utm_medium · utm_campaign · utm_content · utm_term, plus a generic ?ref are all captured

The checkout reads these from the buyer's URL and passes them to the order; no new PII is collected beyond the free-text you put in the link. Aggregate paid revenue by source from a session endpoint:

GET/api/analytics/utm
200 OK · paid orders grouped by source
{
  "totals": { "orders": 42, "revenue": 187300, "currency": "gbp" },
  "bySource": [
    { "source": "instagram", "orders": 20, "revenue": 90000 },
    { "source": "tiktok",    "orders": 12, "revenue": 55300 },
    { "source": "(direct)", "orders": 10, "revenue": 42000 }
  ]
}
# revenue is in minor units, sorted by revenue desc; untagged orders fall under "(direct)"

The dashboard's Sharing → UTM analytics table renders exactly this so you can see, at a glance, which channel paid off.

Express wallets - Apple Pay & Google Pay

On Stripe-powered checkouts, Chargedeck adds Stripe's Payment Request Button above the card form, giving buyers one-tap Apple Pay or Google Pay. It reuses the existing PaymentIntent flow - no change to the server's charge logic - and settles to your own Stripe exactly like a card.

What it looks like: on a supported device the black Apple Pay / Google Pay button appears at the top of the checkout with an "- or pay with card -" divider beneath it. On a device or browser that can't pay, the button simply isn't shown - no error, no empty box.

The button hides gracefully whenever any of these is true: the provider isn't Stripe, it's a preview/demo, the seller isn't Stripe-activated, or the device/browser reports no available wallet.

This is a scaffold - Apple Pay needs domain verification. Google Pay works as soon as your Stripe is live. Apple Pay additionally requires: (a) verifying the domain checkout.2.25.199.135.nip.io in your Stripe dashboard, (b) hosting the association file Stripe gives you at /.well-known/apple-developer-merchantid-domain-association (Chargedeck already serves that path from a file when present), and (c) an activated Stripe processor. Until those are done the Apple Pay button won't appear on Apple devices - and Chargedeck never fakes a success state.

Custom short slugs

Replace the auto-generated name-a1b2 tail with a memorable slug on your pages and payment links, and pick your storefront handle. All three share one validator:

  • Charset & length: lowercase a–z, digits 0–9, and hyphens; 3–40 chars; no leading or trailing hyphen.
  • Reserved names rejected: route names like p, l, s, app, admin, account, api, developers, qr, demo, auth can never be claimed.
  • Unique per namespace: pages are unique among page slugs, links among link slugs, handles among handles. Bad input is rejected with 400, never silently mangled.

Set a custom slug when you create or update a page (field customSlug) or a payment link (field slug). Check availability live before you commit:

GET/api/slug/check?type=page|link|handle&slug=<value>
Responses
{ "ok": true }
{ "ok": false, "reason": "invalid" }   // fails charset/length
{ "ok": false, "reason": "reserved" }  // route-name collision
{ "ok": false, "reason": "taken" }     // already used in that namespace
Changing a page slug changes its URL. A page's old /p/<old-slug> stops resolving once you set a new one - update anywhere you'd already shared it. Payment links resolve by either their id or their custom slug, so /l/plink_9f3c and /l/spring-invoice can point at the same link.

DM automation - "comment SHOP → auto-DM your link"

A classic Instagram/Facebook growth loop: post a reel, ask people to comment a keyword, and a bot DMs them your buy link. Chargedeck holds no funds and runs no bot - you just hand ManyChat a link you already own (your storefront /s/<handle> or a single payment link). Recipe:

  1. In ManyChat, create an Instagram (or Facebook) Comment Automation - or a Story / Keyword trigger.
  2. Trigger: pick the specific post/reel and set the keyword to SHOP (or LINK / BUY).
  3. Action → Send DM: include your Chargedeck link:
    The DM link
    https://checkout.2.25.199.135.nip.io/s/<handle>
    # …or a single checkout: /p/<slug>  ·  /l/<id-or-slug>
  4. Optional - pull the link dynamically: add a ManyChat External Request step that GETs a tiny public endpoint so the DM always carries your current URL/title:
    GET/api/social/link/:handle
    200 OK · public, no auth
    {
      "url":   "https://checkout.2.25.199.135.nip.io/s/jane-makes",
      "title": "Jane Makes",
      "bio":   "Ceramics & workshops"
    }
    # 404 when the storefront is missing or unpublished · no secrets, no PII
Add UTM for credit. DM a tagged link like …/s/jane-makes?utm_source=instagram&utm_medium=dm and every resulting sale shows up under instagram in GET /api/analytics/utm - closing the loop from comment to paid order.

Native ManyChat integration - post-purchase DM

The recipe above only covers the inbound half: ManyChat DMs your link. Chargedeck now closes the other half natively - when that buyer pays, Chargedeck DMs them a thank-you itself, with no Zap in the middle. The full loop:

01
Comment
Someone comments SHOP on your reel. ManyChat catches the keyword.
02
DM the link
ManyChat DMs your Chargedeck link with ?mc_id={{user_id}} appended.
03
Pay
Buyer checks out. The mc_id rides along to the order. Money settles to your processor.
04
Thank-you DM
Chargedeck calls ManyChat and the buyer gets a DM - automatically.

Step 1 - DM the link with the subscriber id attached

In your ManyChat flow, append mc_id={{user_id}} to whichever Chargedeck link you send. {{user_id}} is ManyChat's own merge field for the subscriber - it resolves to their subscriber id as the DM goes out:

The DM link · with subscriber id
https://checkout.2.25.199.135.nip.io/s/your-handle?mc_id={{user_id}}
# works on any Chargedeck link: /s/<handle>  ·  /p/<slug>  ·  /l/<id-or-slug>
# combine it with UTM as usual: ?mc_id={{user_id}}&utm_source=instagram&utm_medium=dm

Chargedeck captures mc_id from the buyer's URL and carries it through storefront → checkout → order, exactly like a UTM param. If you DM a storefront link, you don't need to tag each button - storefront links propagate mc_id to the checkout automatically when the buyer taps through.

No mc_id, no DM. This is by design, not a bug. Chargedeck has no other way to know who the buyer is on Instagram - a card payment carries an email at best, never a subscriber id. If the link the buyer clicked had no mc_id on it, the order still completes normally and simply no DM is sent.

Step 2 - connect ManyChat in Chargedeck

Go to Dashboard → Developers → "ManyChat - post-purchase DM" and fill in three fields:

FieldRequiredNotes
ManyChat API keyyesFrom ManyChat → Settings → API → Generate your API Key. Requires a ManyChat paid plan - the API is not on the free tier.
Tag IDnoA ManyChat tag applied to every buyer, so you can segment "has bought" in ManyChat.
Message templatenoThe DM text. Supports {{product}}, {{ref}} and {{amount}}.
Message template · placeholders
Thanks for grabbing {{product}}! Your order ref is {{ref}} ({{amount}}).
Any questions, just reply here.

# {{product}} → the page/link description  ·  {{ref}} → your order reference
# {{amount}} → the order total, already formatted for its currency

There's a Send test button next to the fields: give it a subscriber ID and it posts a real message through your key, then shows you ManyChat's raw response - status and body, unedited. Use it to prove the key, the tag and the template all work before you point a live campaign at it.

Step 3 - what Chargedeck sends on payment

The moment an order with an mc_id becomes paid, Chargedeck makes exactly this call - no queue, no middleman:

HTTP · outbound to ManyChat
POST https://api.manychat.com/fb/sending/sendContent
Authorization: Bearer <your ManyChat API key>
Content-Type: application/json

{
  "subscriber_id": "<mc_id from the link>",
  "data": {
    "version": "v2",
    "content": {
      "messages": [
        { "type": "text", "text": "Thanks for your order! ..." }
      ]
    }
  },
  "message_tag": "POST_PURCHASE_UPDATE"
}
Why message_tag matters. Instagram/Meta normally forbid messaging a user more than 24 hours after their last interaction with you - and a buyer who comments, gets a DM, then takes their time checking out can easily fall outside that window. POST_PURCHASE_UPDATE is Meta's sanctioned tag for exactly this case: a transactional update about an order the person actually placed. Sending with it is what lets the thank-you DM land regardless of the 24-hour rule.

If you configured a Tag ID, Chargedeck additionally calls:

HTTP · optional, only when a Tag ID is set
POST https://api.manychat.com/fb/subscriber/addTag
Authorization: Bearer <your ManyChat API key>
Content-Type: application/json

{ "subscriber_id": "<mc_id from the link>", "tag_id": 123456 }
Instagram really does use the /fb/ namespace. ManyChat serves its Instagram endpoints from the legacy /fb/ path - that's ManyChat's own naming, not a copy-paste slip in these docs and not a sign your Instagram flow is wired to Facebook by mistake. Expected, not a bug.

Reliability - what this can and can't do to your orders

  • Fire-and-forget. The DM can never delay, block, or fail a payment. It is dispatched after the order is already recorded paid - if ManyChat is down, slow, or your key is wrong, the order still completes normally. Payment is never held hostage to a chat vendor.
  • Once per order. The send is idempotent across all payment paths (Stripe, PayPal, Square, webhook replays, manual reconciliation), so a buyer gets one thank-you, not four.
  • Failures are visible, not silent. The outcome - ok, the HTTP status, or the error - is recorded on the order itself. If DMs stop landing you can see why on the order rather than guessing.
  • Nothing custodial changes. This is a message, not money. The charge still settles straight to your own Stripe / PayPal / Square.

Alternative - drive the same loop from the webhook

Prefer not to paste a ManyChat API key into Chargedeck? You don't have to. The signed order.paid webhook carries everything the DM needs, so you can run the identical loop through Zapier / Make / n8n - verify the signature, read your reference and amount, and call sendContent from there with your own key. Same result; the key stays in the tool you already trust with it. The native integration just saves you the hop.