{"id":350,"date":"2026-08-29T21:00:00","date_gmt":"2026-08-29T18:00:00","guid":{"rendered":"https:\/\/john-nessime.com\/blog\/?p=350"},"modified":"2026-09-14T16:13:04","modified_gmt":"2026-09-14T13:13:04","slug":"unify-shopify-stripe-crm-support-data","status":"publish","type":"post","link":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/","title":{"rendered":"One Customer, Four Systems: Unifying Shopify, Stripe, CRM and Support Data"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The project usually starts with a screenshot. Someone in support pastes three CRM records into a channel, all carrying the same person&#8217;s name, and asks which one is real. One has the order history. One has the subscription. The third has four tickets attached and no orders at all.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nobody broke anything. Shopify, Stripe, the CRM and the helpdesk each created a customer record using whatever identifier they had at the moment they needed one, and none of those identifiers agreed. The systems are all correct in isolation. The person is the thing that got lost.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This post covers what actually breaks when you unify Shopify, Stripe, CRM and support data, and how to build the join so it survives guest checkouts, duplicate webhooks, refunds, currency conversion and deletion requests. It&#8217;s organized by failure family rather than by tool, because the tools get acquired and renamed every couple of years and the failures never change.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Failure family one: the join key is not the email address<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Every one of these systems will happily give you an email address, which is exactly why so many pipelines join on it and quietly produce garbage.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Start with where identity actually lives in each system:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Shopify<\/strong> has a customer ID, but an order does not have to be attached to one. Guest checkout produces an order with an email on it and no durable customer record behind it.<\/li>\n\n<li><strong>Stripe<\/strong> has a customer ID, but only if a Customer object was created. Checkout Sessions that don&#8217;t create one are associated with guest customers instead, which Stripe groups in the Dashboard based on the same card, email or phone. That grouping is read-only and it is not an ID you can join on.<\/li>\n\n<li><strong>The CRM<\/strong> keys on email in almost every default configuration, and cheerfully creates a second contact when the email differs by one character.<\/li>\n\n<li><strong>The helpdesk<\/strong> keys on the requester&#8217;s email, which is whatever the person typed into a contact form or sent the ticket from. It is very often not the address on the order.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Here&#8217;s the part that stays invisible until someone finally checks: nothing errors. Every dashboard still returns a number. Lifetime value gets silently split across three profiles, so your LTV looks lower and your repeat-purchase rate looks worse than it is. Support metrics look <em>better<\/em> than they are, because a customer coming back with the same unresolved problem shows up as a brand new contact. You can run like that for a year without a single failed job.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Build an identifier table, not a fuzzy match<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The fix is boring and it works: a surrogate person key, and a separate table of every identifier ever observed for that person, with the source that reported it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- One row per real human, generated by you, owned by you.\nCREATE TABLE person (\n  person_key      uuid PRIMARY KEY,\n  created_at      timestamptz NOT NULL DEFAULT now(),\n  merged_into     uuid NULL REFERENCES person(person_key)\n);\n\n-- One row per identifier per source. This is the whole trick.\nCREATE TABLE person_identifier (\n  person_key      uuid NOT NULL REFERENCES person(person_key),\n  source          text NOT NULL,   -- shopify | stripe | crm | helpdesk\n  id_type         text NOT NULL,   -- email | phone | shopify_customer_id | stripe_customer_id\n  id_value        text NOT NULL,   -- already normalized\n  first_seen_at   timestamptz NOT NULL,\n  last_seen_at    timestamptz NOT NULL,\n  PRIMARY KEY (source, id_type, id_value)\n);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Normalization happens before the row is written, not in the join. Lowercase and trim emails, store phone numbers in E.164, strip whitespace from external IDs. Resist the urge to get clever with Gmail&#8217;s dot and plus handling. Stripping <code>+tags<\/code> is a business policy decision with real consequences, not a technical detail, and the same rule applied to a domain that treats plus addresses as distinct mailboxes will merge two genuinely different people.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deterministic matching first, always. Exact matches on shared identifiers, transitively closed. Probabilistic matching on name plus address plus device is worth reaching for only when you have exhausted the deterministic keys, and it has an asymmetric failure mode: a household sharing one card gets collapsed into one person, and unmerging afterwards is expensive because every downstream system has already copied the merged key into its own records. Merges are cheap to make and painful to undo. Bias toward leaving two records separate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One thing worth doing early on the Stripe side: when you create a Customer, write your own identifier into <code>metadata<\/code>. Stripe allows fifty key-value pairs per object, with keys up to forty characters and values up to five hundred, and it only returns metadata for requests made with a secret key. That&#8217;s enough room for a person key and an order reference, which turns a future fuzzy join into an exact one. Never put anything sensitive there.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Failure family two: webhooks arrive twice, out of order, or not at all<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Both platforms are explicit about this, and both get treated as if they weren&#8217;t.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Stripe delivers at least once, does not guarantee ordering, and retries failed deliveries with exponential backoff for up to three days. The event <code>id<\/code> (the one starting <code>evt_<\/code>) stays identical across every retry, which makes it your deduplication key. Shopify sends <code>X-Shopify-Webhook-Id<\/code> for the same purpose, signs the raw body into <code>X-Shopify-Hmac-Sha256<\/code>, and states plainly that ordering isn&#8217;t guaranteed within a topic or across topics for the same resource. Shopify&#8217;s own documentation goes further and tells you not to rely on webhooks as your only source of data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a customer-data pipeline specifically, out-of-order delivery is nastier than duplication. Consider a <code>customers\/update<\/code> that changes someone&#8217;s email arriving before the <code>orders\/create<\/code> that was placed under the old address. Process those in arrival order and you attach the order to a person who doesn&#8217;t exist yet, or worse, to the wrong one. Order your writes by the event&#8217;s own timestamp, using <code>X-Shopify-Triggered-At<\/code> or the <code>updated_at<\/code> in the payload, and make profile updates last-writer-wins on that timestamp rather than on insertion time.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do almost nothing in the handler<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Verify the signature, claim the event ID, write the raw body to durable storage, return 2xx. That&#8217;s it. Identity resolution, CRM lookups and enrichment calls all belong in a worker reading from that store. A handler that tries to resolve identity inline will eventually be slow enough to time out, which triggers a retry, which arrives while the first attempt is still running.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Make the claim atomic rather than check-then-act, because two retries genuinely can land concurrently:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Atomic claim. If this inserts zero rows, someone already has it.\nINSERT INTO webhook_event (source, event_id, topic, received_at, payload)\nVALUES ($1, $2, $3, now(), $4)\nON CONFLICT (source, event_id) DO NOTHING\nRETURNING event_id;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Return a 2xx for a duplicate. A duplicate is a success from the sender&#8217;s point of view, and returning an error just schedules more of them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Signature verification has one recurring trap on the Shopify side: it runs against the <em>raw<\/em> body. Any JSON body parser that runs first will change the bytes and every signature will fail. Capture the raw body before parsing, and compare in constant time.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import base64, hashlib, hmac\n\ndef verify_shopify(raw_body: bytes, header_value: str, client_secret: str) -&gt; bool:\n    digest = hmac.new(\n        client_secret.encode(\"utf-8\"),\n        raw_body,\n        hashlib.sha256,\n    ).digest()\n    expected = base64.b64encode(digest).decode(\"utf-8\")\n    return hmac.compare_digest(expected, header_value)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Worth knowing before you spend an afternoon on it: after rotating an app&#8217;s client secret, Shopify notes it can take up to an hour before signatures are generated with the new one. If verification starts failing right after a rotation, that&#8217;s usually why.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And because delivery isn&#8217;t guaranteed, webhooks alone are never enough. Run a reconciliation job that pulls orders and customers modified since the last watermark and fills the gaps. This is the job that saves you during an outage on your side, and it&#8217;s the one people skip. If you&#8217;d rather not build the buffering and replay layer, managed webhook infrastructure like Hookdeck exists for exactly this, and self-hosting a small queue and worker on a modest VPS from somewhere like Contabo or InterServer is perfectly reasonable too. The point is that something has to catch what the webhook missed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Failure family three: &#8220;revenue&#8221; means four different things<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the one that turns into a meeting. Finance says the number is wrong. Analytics says the number is right. Both are correct, because they&#8217;re measuring different things and nobody wrote down which.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Shopify reports gross sales at the moment the order is placed, in the store&#8217;s currency and the store&#8217;s timezone.<\/li>\n\n<li>Stripe pays out in batches, net of processing fees, refunds and disputes, on its own schedule, in the settlement currency of the account.<\/li>\n\n<li>A refund issued in Shopify and a refund issued in Stripe are two different events that may or may not be the same underlying money movement.<\/li>\n\n<li>A dispute lands days or weeks after the order and reverses revenue in a period that has already been reported.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s a structural trap here too. If the store runs Shopify Payments, there is no Stripe account for you to query. Shopify Payments is built on Stripe&#8217;s processing infrastructure, but it&#8217;s Shopify&#8217;s gateway, and payouts, fees and disputes live in the Shopify admin rather than behind a Stripe API key. Plenty of businesses end up with Shopify Payments handling storefront orders and a separate Stripe account handling subscriptions or invoicing, which means two payment systems, two payout schedules and one customer who has no idea any of this exists.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do not build a single <code>revenue<\/code> column. Build separate fact tables at their natural grain (orders, order line items, payments, refunds, disputes, payouts) plus a bridging table that links a payout back to the transactions inside it. Then let each team define its own metric on top. Finance gets net settled cash. Marketing gets gross booked revenue. Neither has to be wrong for the other to be right.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Store every timestamp in UTC and keep the original timezone as a separate column. Store every amount in minor units as an integer alongside its currency code, and store the FX rate that was actually applied rather than recalculating it later from a rates table. Recalculated FX is the single most common source of &#8220;the number moved and nobody changed anything.&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On ingestion, Stripe Data Pipeline can push Stripe data directly into Snowflake, Redshift or Databricks as a data share, which removes a connector from the picture if you&#8217;re already on one of those. Otherwise a managed ELT tool like Fivetran or a self-hosted Airbyte will handle Shopify, Stripe, the CRM and the helpdesk with off-the-shelf connectors. Both routes are fine. The connector is genuinely the easy part.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Failure family four: deletion is the requirement nobody models<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re building this as a Shopify app, three compliance webhooks are mandatory: <code>customers\/data_request<\/code>, <code>customers\/redact<\/code> and <code>shop\/redact<\/code>. You acknowledge with a 200 and complete the action within thirty days. <code>shop\/redact<\/code> fires forty-eight hours after uninstall and covers the shop&#8217;s data. These are enforced at app review, so you&#8217;ll find out about them either way.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The interesting problem isn&#8217;t receiving the webhook. It&#8217;s that by the time it arrives, that person&#8217;s email exists in your raw landing zone, your staging tables, your identity table, your customer view, a BI extract, the CRM, the helpdesk, and a Slack message from a debugging session six months ago. A delete that only touches the golden record is not a delete.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The design decision that makes this tractable is to hold personal data in exactly one place. Every downstream table carries <code>person_key<\/code> and nothing else. Facts, aggregates, models, extracts and syncs all reference the key. When a redaction request lands, you redact one row set in the identifier table and the rest of the warehouse is already anonymous. Getting this right at schema-design time costs an afternoon; retrofitting it costs a quarter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Practical pieces that make it real:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Tag every table and column that can contain an identifier, in your catalog or in dbt metadata, and treat the tag list as the deletion scope.<\/li>\n\n<li>Partition the raw landing zone so you can rewrite a partition rather than scan an entire bucket looking for one email.<\/li>\n\n<li>Keep a deletion queue with a status per downstream system, including the CRM and the helpdesk, which have their own APIs and their own idea of what deletion means.<\/li>\n\n<li>Write down what you retain and why. Financial records generally have to survive a deletion request for tax and dispute reasons. Say so rather than promising a hard delete you can&#8217;t perform.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Failure family five: pushing the unified view back out<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A customer view nobody can act on is a report. The value shows up when a support agent opens a ticket and can see subscription status and lifetime order count without switching tabs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s reverse ETL, and the market is well served: Hightouch, Census (now part of Fivetran), Polytomic, RudderStack, and iPaaS-style tools like Workato if you need branching logic rather than field syncs. The warehouse-native argument is straightforward and mostly correct: your models already live there, and you avoid maintaining a second copy of the customer profile inside a vendor&#8217;s rigid schema. The counter-argument is also real. Reverse ETL is batch by nature, so if you need sub-minute reaction to an event, a packaged CDP or a direct event pipeline will serve you better, and you&#8217;ll be running several vendors plus warehouse compute plus the people to maintain the models.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Three things go wrong here more or less every time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The write loop.<\/strong> You compute a churn-risk field and sync it into the CRM. Your ELT connector reads the CRM back into the warehouse. Now the computed field is an input to the model that computes it. This is genuinely hard to spot because nothing errors, the value just drifts. Namespace every synced field with a consistent prefix and explicitly exclude that namespace from ingestion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>The resync stampede.<\/strong> Incremental syncs push diffs and stay small. Change a model definition and the next run wants to update every record, which will meet a helpdesk API rate limit at speed. Cap the batch size, and treat a full resync as a planned operation rather than something a merge to main can trigger.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Syncing logic instead of keys.<\/strong> Push <code>person_key<\/code> into a custom field on the CRM contact and the helpdesk user. It makes the key searchable by agents, it gives the next pipeline a deterministic join, and it stops three teams from each rebuilding their own version of your matching rules.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A pipeline shape that holds up<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Put together, the order of operations matters more than the tool choice:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Land raw. Every webhook body and every API page goes to immutable storage first, keyed by source and event ID, before anything interprets it.<\/li>\n\n<li>Deduplicate on the provider&#8217;s event ID, atomically, with a retention window longer than the provider&#8217;s retry window.<\/li>\n\n<li>Normalize identifiers into the identifier table as rows arrive, with the source recorded.<\/li>\n\n<li>Resolve identity deterministically into <code>person_key<\/code>, as a scheduled job, never inside a request handler.<\/li>\n\n<li>Build fact tables per system at their own grain, keyed on <code>person_key<\/code>, with no raw PII.<\/li>\n\n<li>Model the customer view on top, in dbt or whatever your team already uses. The Fivetran <code>dbt_customer360<\/code> package is a reasonable starting point if you want to see how someone else structured the identity step.<\/li>\n\n<li>Activate a small, named, namespaced set of fields back into the CRM and helpdesk.<\/li>\n\n<li>Run reconciliation and freshness checks on a schedule, and alert on them like you would any other production job.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting: symptom to root cause<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">These are the ones that come up repeatedly, and the check that usually settles them.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Warehouse customer count is much higher than Shopify&#8217;s.<\/strong> Guest orders are minting a new person on every purchase. Check how many <code>person<\/code> rows have exactly one identifier and one order.<\/li>\n\n<li><strong>Warehouse count is much lower.<\/strong> An over-eager merge rule. Look for person keys with an implausible number of distinct emails or shipping addresses attached.<\/li>\n\n<li><strong>BI revenue doesn&#8217;t match Stripe payouts.<\/strong> Grain mismatch, not a data loss. Reconcile a single payout against its constituent transactions before touching anything else.<\/li>\n\n<li><strong>A CRM field keeps flipping between two values.<\/strong> Write loop. Check whether the field&#8217;s namespace is excluded from your ELT source configuration.<\/li>\n\n<li><strong>Recent orders missing, older ones fine.<\/strong> Webhook delivery gap. Confirm the reconciliation job ran, and check its watermark rather than trusting its exit code.<\/li>\n\n<li><strong>Every Shopify signature suddenly fails.<\/strong> Either a body parser is running before verification, or the client secret was rotated within the last hour.<\/li>\n\n<li><strong>Tickets attached to the wrong customer after a merge.<\/strong> The merge happened in the warehouse but the synced key in the helpdesk still points at the old person. Merges need to propagate outward, not just inward.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Common mistakes<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Joining on email in the model layer instead of resolving identity once, upstream.<\/li>\n\n<li>Treating webhooks as the system of record instead of as a low-latency hint, with a reconciliation pull behind them.<\/li>\n\n<li>Doing identity resolution, enrichment or CRM writes inside the webhook handler.<\/li>\n\n<li>Ordering profile updates by arrival time rather than by the event&#8217;s own timestamp.<\/li>\n\n<li>One <code>revenue<\/code> column that finance and marketing are both told to use.<\/li>\n\n<li>Storing amounts as floats, or recalculating historical FX from a rates table.<\/li>\n\n<li>Copying PII into every downstream table because it was convenient at the time.<\/li>\n\n<li>Reaching for probabilistic matching before the deterministic keys have been exhausted.<\/li>\n\n<li>Assuming a Shopify store on Shopify Payments has a Stripe account you can query.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Best practices worth the effort<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Own the surrogate key. Never let a vendor&#8217;s internal ID become the primary identity of a person in your systems.<\/li>\n\n<li>Write your <code>person_key<\/code> into Stripe <code>metadata<\/code> and Shopify customer metafields at creation time, so future joins are exact rather than inferred.<\/li>\n\n<li>Keep raw payloads immutable and replayable. Every model bug you&#8217;ll ever have is cheaper to fix if you can reprocess from raw.<\/li>\n\n<li>Version your matching rules and record which version produced each merge, so you can audit and reverse a bad rule.<\/li>\n\n<li>Alert on freshness and on row-count deltas per source, not just on job success. A connector returning zero rows exits cleanly.<\/li>\n\n<li>Reconcile one payout end to end by hand before you trust the automated version. It&#8217;s the fastest way to find a grain error.<\/li>\n\n<li>Document the metric definitions next to the models. Most &#8220;the data is wrong&#8221; tickets are definition disputes.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need a CDP to unify Shopify, Stripe, CRM and support data?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Not necessarily. If you already run a warehouse and have someone comfortable with dbt, ELT plus modelling plus reverse ETL covers the same ground with more control over the data model. A packaged CDP earns its cost when you need real-time behavioural segmentation, when nobody on the team can maintain models, or when marketing needs to ship changes without waiting on data engineering.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I handle guest checkout customers?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Create a person from the order&#8217;s email anyway, and mark the record as identity-weak. When that email later appears attached to a real Shopify customer or Stripe Customer object, the deterministic match links them and the order history follows. The mistake is discarding guest orders because they have no customer ID, which is exactly the revenue you most want attributed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should I sync everything into the CRM or keep it in the warehouse?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the full picture in the warehouse and sync a deliberately small set of fields outward. The test is whether an agent or a rep will act on the field during a conversation. Lifetime value, subscription status, open ticket count and churn risk pass. A hundred-column profile does not, and it turns every model change into a rate-limit incident.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why don&#8217;t my Shopify sales match my Stripe payouts?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Because they&#8217;re measuring different things at different times. Shopify shows gross sales at order time; Stripe pays out batched amounts net of fees, refunds and disputes, and the timing differs by days. Add currency conversion and a timezone mismatch and the numbers will never tie without an explicit bridging table linking payouts to the transactions inside them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can I use email as the primary key if my store requires account creation?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You can get away with it longer, but it&#8217;s still a bad primary key. People change email addresses, use one address for orders and another for support, and share addresses within a household. A surrogate key with an identifier table costs very little up front and means an email change is a new row rather than a broken join and a duplicate customer.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I test the identity resolution before trusting it?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run it in shadow mode. Compute person keys without activating them, then have support pick twenty customers they know are duplicates and twenty they know are distinct, and check what the resolver did. That exercise finds normalization bugs and over-eager merge rules faster than any aggregate metric, because the people who answer the tickets already know where the duplicates are.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">What&#8217;s the minimum viable version of this?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Raw landing with deduplication, an identifier table with deterministic matching, one fact table per source, and a single synced field into the helpdesk. That&#8217;s a couple of weeks of work and it removes most of the pain. Everything above it is refinement.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion: unify Shopify, Stripe, CRM and support data around a key you own<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you take one thing from this: the hard part of unifying Shopify, Stripe, CRM and support data is not moving the data. Connectors are commodity. The hard part is deciding what a customer <em>is<\/em>, writing that decision down as a key you own, and then refusing to let any downstream system quietly invent its own version of it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Do the identity work first, land everything raw so you can reprocess when you get it wrong, and keep personal data in one place so deletion stays a single operation. The dashboards will follow. Skip that order and you get exactly the screenshot this post opened with, eighteen months later and with a lot more rows behind it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Need help wiring this together?<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I work on exactly this kind of plumbing: the unglamorous layer between an ecommerce platform, a payment processor and the tools your team actually opens all day. Things I can help with here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Designing the identity model (surrogate keys, identifier tables, deterministic matching rules) and auditing an existing one for over-merging.<\/li>\n\n<li>Building idempotent, signature-verified webhook intake for Shopify and Stripe, with a dead-letter path and a reconciliation job behind it.<\/li>\n\n<li>Setting up ELT into Snowflake, BigQuery, Redshift or Postgres, whether with Fivetran, self-hosted Airbyte, or Stripe Data Pipeline where it fits.<\/li>\n\n<li>Modelling orders, payments, refunds, disputes and payouts so finance and marketing can each get a number they trust.<\/li>\n\n<li>Reverse ETL back into the CRM and helpdesk without write loops, rate-limit incidents or a hundred junk fields.<\/li>\n\n<li>Making deletion and data-subject requests actually work across the warehouse, the raw landing zone and every downstream copy.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re stuck on a specific piece, send me the concrete thing: a webhook payload, a schema dump, the query that disagrees with your Stripe payout report. It&#8217;s much easier to say something useful about a real artifact than about a diagram.<\/p>\n\n\n\n<div class=\"wp-block-buttons is-layout-flex wp-block-buttons-is-layout-flex\">\n<div class=\"wp-block-button\"><a class=\"wp-block-button__link wp-element-button\" href=\"https:\/\/www.upwork.com\/freelancers\/~01f15a912ad84a6620\" target=\"_blank\" rel=\"noreferrer noopener\">Work with me on Upwork<\/a><\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Three CRM records, one person, and nothing in the logs to explain it. A practical walkthrough of what actually breaks when you unify Shopify, Stripe, CRM and support data: guest-checkout identity, duplicate and out-of-order webhooks, revenue that never ties to payouts, deletion requests that miss half your copies, and reverse ETL write loops. Organized by failure family, with schema and handler patterns you can apply directly.<\/p>\n","protected":false},"author":1,"featured_media":351,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[498,52],"tags":[535,347,155,231,154,350,225,157,343,313,534,390,533,158,223,532,226,346],"class_list":["post-350","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-data-engineering","category-technical-guides","tag-customer-360","tag-customer-support-analytics","tag-data-integration","tag-data-quality","tag-data-warehouse","tag-dbt","tag-ecommerce-analytics","tag-etl","tag-event-driven-architecture","tag-idempotency","tag-identity-resolution","tag-pii-redaction","tag-reverse-etl","tag-schema-design","tag-shopify","tag-stripe","tag-webhooks","tag-zendesk","entry","has-media"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>How to Unify Shopify, Stripe, CRM and Support Data<\/title>\n<meta name=\"description\" content=\"A practical guide to unify Shopify, Stripe, CRM and support data: identity keys, webhook ordering, revenue grain, deletion and safe activation.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to Unify Shopify, Stripe, CRM and Support Data\" \/>\n<meta property=\"og:description\" content=\"A practical guide to unify Shopify, Stripe, CRM and support data: identity keys, webhook ordering, revenue grain, deletion and safe activation.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/\" \/>\n<meta property=\"og:site_name\" content=\"John Nessime\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-29T18:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-14T13:13:04+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/unify-shopify-stripe-crm-support-data.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"627\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"John Nessime\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"John Nessime\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"17 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/\"},\"author\":{\"name\":\"John Nessime\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\"},\"headline\":\"One Customer, Four Systems: Unifying Shopify, Stripe, CRM and Support Data\",\"datePublished\":\"2026-08-29T18:00:00+00:00\",\"dateModified\":\"2026-09-14T13:13:04+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/\"},\"wordCount\":3719,\"publisher\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\"},\"image\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/unify-shopify-stripe-crm-support-data.png\",\"keywords\":[\"Customer 360\",\"Customer Support Analytics\",\"Data Integration\",\"Data Quality\",\"Data Warehouse\",\"dbt\",\"Ecommerce Analytics\",\"ETL\",\"Event-Driven Architecture\",\"Idempotency\",\"Identity Resolution\",\"PII Redaction\",\"Reverse ETL\",\"Schema Design\",\"Shopify\",\"Stripe\",\"Webhooks\",\"Zendesk\"],\"articleSection\":[\"Data Engineering\",\"Technical Guides\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/\",\"name\":\"How to Unify Shopify, Stripe, CRM and Support Data\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/unify-shopify-stripe-crm-support-data.png\",\"datePublished\":\"2026-08-29T18:00:00+00:00\",\"dateModified\":\"2026-09-14T13:13:04+00:00\",\"description\":\"A practical guide to unify Shopify, Stripe, CRM and support data: identity keys, webhook ordering, revenue grain, deletion and safe activation.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/#primaryimage\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/unify-shopify-stripe-crm-support-data.png\",\"contentUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/unify-shopify-stripe-crm-support-data.png\",\"width\":1200,\"height\":627,\"caption\":\"Diagram contrasting two joins: four source systems joined on email scatter into three fragmented customer profiles, while the same four sources resolved through a person_identifier table converge on a single owned person_key.\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/technical-guides\\\/unify-shopify-stripe-crm-support-data\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"One Customer, Four Systems: Unifying Shopify, Stripe, CRM and Support Data\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/\",\"name\":\"John Nessime\",\"description\":\"Cloud, DevOps, Data &amp; AI \u2014 Built, Tested, Explained\",\"publisher\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/#\\\/schema\\\/person\\\/ede0b56d0c808f123f57d5d796902105\",\"name\":\"John Nessime\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\",\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\",\"contentUrl\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\",\"width\":512,\"height\":512,\"caption\":\"John Nessime\"},\"logo\":{\"@id\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/cropped-jn.png\"},\"description\":\"AWS Certified Solutions Architect helping businesses build reliable cloud, data, reporting, and automation solutions. I help startups, agencies, and growing businesses replace manual processes and disconnected data with practical AWS architectures, clean data pipelines, useful dashboards, and maintainable automation.\",\"sameAs\":[\"https:\\\/\\\/john-nessime.com\\\/blog\",\"https:\\\/\\\/www.linkedin.com\\\/in\\\/john-m-nessime\"],\"url\":\"https:\\\/\\\/john-nessime.com\\\/blog\\\/author\\\/johnnessime\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"How to Unify Shopify, Stripe, CRM and Support Data","description":"A practical guide to unify Shopify, Stripe, CRM and support data: identity keys, webhook ordering, revenue grain, deletion and safe activation.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/","og_locale":"en_US","og_type":"article","og_title":"How to Unify Shopify, Stripe, CRM and Support Data","og_description":"A practical guide to unify Shopify, Stripe, CRM and support data: identity keys, webhook ordering, revenue grain, deletion and safe activation.","og_url":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/","og_site_name":"John Nessime","article_published_time":"2026-08-29T18:00:00+00:00","article_modified_time":"2026-09-14T13:13:04+00:00","og_image":[{"width":1200,"height":627,"url":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/unify-shopify-stripe-crm-support-data.png","type":"image\/png"}],"author":"John Nessime","twitter_card":"summary_large_image","twitter_misc":{"Written by":"John Nessime","Est. reading time":"17 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/#article","isPartOf":{"@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/"},"author":{"name":"John Nessime","@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105"},"headline":"One Customer, Four Systems: Unifying Shopify, Stripe, CRM and Support Data","datePublished":"2026-08-29T18:00:00+00:00","dateModified":"2026-09-14T13:13:04+00:00","mainEntityOfPage":{"@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/"},"wordCount":3719,"publisher":{"@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105"},"image":{"@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/#primaryimage"},"thumbnailUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/unify-shopify-stripe-crm-support-data.png","keywords":["Customer 360","Customer Support Analytics","Data Integration","Data Quality","Data Warehouse","dbt","Ecommerce Analytics","ETL","Event-Driven Architecture","Idempotency","Identity Resolution","PII Redaction","Reverse ETL","Schema Design","Shopify","Stripe","Webhooks","Zendesk"],"articleSection":["Data Engineering","Technical Guides"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/","url":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/","name":"How to Unify Shopify, Stripe, CRM and Support Data","isPartOf":{"@id":"https:\/\/john-nessime.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/#primaryimage"},"image":{"@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/#primaryimage"},"thumbnailUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/unify-shopify-stripe-crm-support-data.png","datePublished":"2026-08-29T18:00:00+00:00","dateModified":"2026-09-14T13:13:04+00:00","description":"A practical guide to unify Shopify, Stripe, CRM and support data: identity keys, webhook ordering, revenue grain, deletion and safe activation.","breadcrumb":{"@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/#primaryimage","url":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/unify-shopify-stripe-crm-support-data.png","contentUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/08\/unify-shopify-stripe-crm-support-data.png","width":1200,"height":627,"caption":"Diagram contrasting two joins: four source systems joined on email scatter into three fragmented customer profiles, while the same four sources resolved through a person_identifier table converge on a single owned person_key."},{"@type":"BreadcrumbList","@id":"https:\/\/john-nessime.com\/blog\/technical-guides\/unify-shopify-stripe-crm-support-data\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/john-nessime.com\/blog\/"},{"@type":"ListItem","position":2,"name":"One Customer, Four Systems: Unifying Shopify, Stripe, CRM and Support Data"}]},{"@type":"WebSite","@id":"https:\/\/john-nessime.com\/blog\/#website","url":"https:\/\/john-nessime.com\/blog\/","name":"John Nessime","description":"Cloud, DevOps, Data &amp; AI \u2014 Built, Tested, Explained","publisher":{"@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/john-nessime.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/john-nessime.com\/blog\/#\/schema\/person\/ede0b56d0c808f123f57d5d796902105","name":"John Nessime","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png","url":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png","contentUrl":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png","width":512,"height":512,"caption":"John Nessime"},"logo":{"@id":"https:\/\/john-nessime.com\/blog\/wp-content\/uploads\/2026\/07\/cropped-jn.png"},"description":"AWS Certified Solutions Architect helping businesses build reliable cloud, data, reporting, and automation solutions. I help startups, agencies, and growing businesses replace manual processes and disconnected data with practical AWS architectures, clean data pipelines, useful dashboards, and maintainable automation.","sameAs":["https:\/\/john-nessime.com\/blog","https:\/\/www.linkedin.com\/in\/john-m-nessime"],"url":"https:\/\/john-nessime.com\/blog\/author\/johnnessime\/"}]}},"_links":{"self":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts\/350","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/comments?post=350"}],"version-history":[{"count":1,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts\/350\/revisions"}],"predecessor-version":[{"id":352,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/posts\/350\/revisions\/352"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/media\/351"}],"wp:attachment":[{"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/media?parent=350"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/categories?post=350"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/john-nessime.com\/blog\/wp-json\/wp\/v2\/tags?post=350"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}