Skip to Content
SourcesShopifyDestinationsOneSignal: Custom Forms

OneSignal: Custom Newsletter Forms

If you’ve built your own newsletter capture — a popup, an inline banner, a footer signup, anything that isn’t a stock Shopify form — you can have Vendo route that email through to OneSignal as a real Email subscription without writing any OneSignal code yourself.

This article covers the two integration paths, what Vendo does automatically, where the email lands in OneSignal, and how to test the flow end-to-end.


Prerequisites

Before any of this works, three things must be true on the storefront:

  1. The Vendo Shopify app is installed and the OneSignal destination is configured (App ID entered in Vendo > Integrations > OneSignal).
  2. The Vendo theme app embed is enabled in the active theme: Online Store > Themes > Customize > App embeds > Vendo: ON. This is what loads the OneSignal Web SDK and Vendo’s newsletter listener.
  3. You have decided on a user_id_type in the Vendo OneSignal config — usually shopify_customer_id. This determines how Vendo identifies the user in OneSignal once the Shopify customer record exists.

Without (2) in particular, your form will submit fine to your own backend but nothing will reach OneSignal.


The simplest integration. You write a standard HTML form with two specific traits, and Vendo’s theme listener does the rest.

The contract

Vendo’s storefront listener scans the page for forms matching any of these selectors:

form[action^="/contact#"] form.newsletter-form form.klaviyo-form form[id*="newsletter" i] form[class*="newsletter"]

For each matched form, Vendo attaches a submit event listener that reads the value from input[type="email"] inside the form. So your form must:

  • Be a real <form> element (not a <div> with an input).
  • Have an id or class whose name contains newsletter (or match one of the other selectors above).
  • Contain an <input type="email">.

That’s all. Your submit handler can call event.preventDefault() and post the email to your own backend via fetch() — Vendo’s listener fires on the submit event itself, before preventDefault matters.

What Vendo does when it detects a submission

  1. Captures the email and writes a record into localStorage.vendo_identity with _pending_event = 'newsletter_registered'.
  2. Calls OneSignal.User.addTags({ email }) against the SDK that the Vendo theme embed loaded — sets the email tag on the current OneSignal user.
  3. Calls OneSignal.User.addEmail(email) — creates a real Email subscription channel on that user, with enabled: true. The user becomes sendable via email.
  4. If user_id_type = 'shopify_customer_id', polls /apps/vendo/customer-lookup?email=... (the Vendo app proxy) at t=0, 2s, 6s, 14s. When Shopify returns a customer ID for that email, Vendo calls OneSignal.login(<shopify_customer_id>) — this transfers the user (with the email subscription already attached) onto the customer-ID-keyed record.
  5. If the customer doesn’t exist yet (typical for first-time signups), Vendo persists _vendo_pending_lookup in localStorage and retries on each subsequent page load (up to 5 cross-page attempts).
  6. On the next Vendo pixel event after submission, fires a Newsletter Registered event into every connected destination (OneSignal custom event, Mixpanel, Segment, Customer.io, etc.) with the email attached.

Minimal example

A working inline newsletter banner that satisfies the contract:

<form class="newsletter-form" id="my-newsletter"> <input type="email" name="email" placeholder="you@example.com" required /> <button type="submit">Subscribe</button> </form> <script> document.getElementById('my-newsletter').addEventListener('submit', function (e) { e.preventDefault(); var email = e.target.querySelector('input[type="email"]').value.trim(); if (!email) return; // Post to your own backend — Vendo has already captured the email by this point. fetch('/your/backend/endpoint', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email }) }); }); </script>

That’s it. No OneSignal App ID anywhere in your code — Vendo provides it from the shop metafield. No OneSignal SDK loading — Vendo loads it. No identify calls — Vendo handles identity, including the deferred customer-ID resolution.

What lands in OneSignal

A single user record (the existing push subscriber’s record if the visitor has previously allowed push, or a brand-new anonymous user otherwise) with:

  • An Email subscription channel with token = <email> and enabled = true
  • A tag email = <email>
  • After the customer-lookup resolves: external_id = <shopify_customer_id>

The email subscription is attached to the existing user record — it does not create a separate “email-only” user. This is intentional and keeps push + email + future purchases all merged on one user record.


Path B — Manual identification

Use Path B when:

  • Your popup is not a <form> (it’s a <div> with inputs)
  • You can’t add newsletter to the class/id (theming constraints)
  • You want to capture the email at a different point than form submit (e.g. on a multi-step modal’s “next” button)

Path B writes the same Vendo data structure that Path A writes — just from your own code instead of via auto-detect.

What to write

function vendoCaptureNewsletter(email) { var data = JSON.parse(localStorage.getItem('vendo_identity') || '{}'); data.email = email; data.newsletter_subscribed = true; data.newsletter_subscribed_at = new Date().toISOString(); data._pending_event = 'newsletter_registered'; localStorage.setItem('vendo_identity', JSON.stringify(data)); // Triggers the redirect-page customer-ID lookup on the next page load localStorage.setItem('_vendo_nl_email', email); }

This alone fires Newsletter Registered into all destinations on the next pixel event. It does not by itself attach an Email subscription to the OneSignal user — for that, also call:

window.OneSignalDeferred = window.OneSignalDeferred || []; window.OneSignalDeferred.push(function (OneSignal) { try { OneSignal.User.addTags({ email: email }); OneSignal.User.addEmail(email); // Do NOT call OneSignal.login(email) when user_id_type = 'shopify_customer_id'. // Vendo will call login() with the resolved customer_id after the lookup completes. } catch (e) { /* push permission may be blocked — addEmail still works */ } });

Then call both functions from your custom UI:

yourCustomPopup.onSubmit(function (email) { vendoCaptureNewsletter(email); // (the OneSignalDeferred block above runs once per page load, not per submit) // ...your own backend POST here });

OneSignal.User.addEmail() is idempotent, so even if your code and Vendo’s auto-detect both run on the same submit there’s no harm — both calls hit the same subscription endpoint and the second returns 409, which Vendo treats as success.

Important: do not call OneSignal.login(email)

With user_id_type = 'shopify_customer_id', the only valid argument to OneSignal.login() is the Shopify customer ID. Calling login(email) burns the email as a permanent external_id alias that OneSignal v16 cannot rename later — the user record becomes orphaned from the customer record at checkout.

Vendo defers login() until the customer lookup resolves to a real Shopify customer ID. Let it.


What Vendo does not do

ConcernOwner
GDPR / CAN-SPAM consent capture, storage, audit trailYou — Vendo does not set any consent_status or opt-in timestamp
Backend email storage (for re-engagement, exports, etc.)You — the fetch() to your own endpoint is yours alone
Email validation / disposable-domain filteringYou (or your backend)
Double opt-in confirmation emailsYou / OneSignal Journeys
Unsubscribe link / preferences pageYou / OneSignal

For consent in particular, the recommended pattern is to set your own tags from the same submit handler:

OneSignal.User.addTags({ marketing_consent: 'granted', marketing_consent_at: new Date().toISOString(), marketing_consent_source: 'site_newsletter_popup_v1' });

This persists alongside the email subscription and gives you an auditable property to filter on in OneSignal segments.


Where to find the user in the OneSignal dashboard

After a test submission:

  1. Open the dashboard at https://dashboard.onesignal.com/apps/<your-app-id>/users. Make sure the App ID matches the one configured in Vendo — newsletter signups go to whichever app you’ve configured, and it’s easy to be looking at the wrong app.
  2. In the User Records page, change the search filter dropdown from External ID to Email.
  3. Search for the test email. The user appears with an Email channel and email tag matching the test value.
  4. If the visitor was already a push subscriber on this browser, the email subscription is attached to that same user record — the Users count doesn’t increment, the user simply gains another subscription channel.
  5. Until the Shopify customer record for that email exists, external_id stays empty (or stays whatever it was from previous identification on that browser). Once Vendo’s customer-lookup resolves the Shopify customer ID, external_id updates to the Shopify customer ID and the user merges with any other identification of the same customer.

Why your tests sometimes look like “no user was created”

The most common confusions, in order of frequency:

  • Wrong app. Multiple OneSignal apps in the same OneSignal account, looking at the wrong one. Confirm the dashboard URL’s App ID matches OneSignal.config.appId in the storefront’s DevTools console.
  • Email filter not selected. The Users page defaults to filtering by External ID — a search for the email returns nothing because External ID = email isn’t true with user_id_type = 'shopify_customer_id'.
  • Test email isn’t a real Shopify customer. Vendo never sets external_id = email in customer-ID mode, so the user record has external_id = null (or stale from prior tests) until either you manually create a Shopify customer with that email, or the visitor goes through checkout.
  • Dashboard indexing delay. The REST API accepts the subscription within milliseconds (HTTP 201), but the dashboard Users view can take a couple of minutes to surface a new subscription.

Testing the flow

  1. Open the storefront in an incognito window and append ?vendo_debug=1 to the URL. See Debug Mode for what to expect in the console.
  2. Open DevTools > Network and filter to onesignal.com.
  3. Submit your custom form with a fresh test email.
  4. You should see, in order:
    • Console: [Vendo] Newsletter email captured: <email>
    • Console: [Vendo] Newsletter: resolving customer ID...
    • Console: [Vendo] Newsletter: OneSignal anonymous — email subscription added
    • Network: PATCH https://api.onesignal.com/apps/<app_id>/users/by/onesignal_id/<onesignal_id> returning 202 (sets email tag)
    • Network: POST https://api.onesignal.com/apps/<app_id>/users/by/onesignal_id/<onesignal_id>/subscriptions returning 201 (creates Email subscription, response body contains the new subscription’s id)
  5. Open OneSignal dashboard at https://dashboard.onesignal.com/apps/<app_id>/users, set the filter to Email, search the test email — the user is there with an Email channel.

If you want to confirm the App ID Vendo is actually sending to (vs. what’s displayed in the Vendo admin), run this in the storefront’s DevTools console:

OneSignal.config.appId

That returns the App ID the SDK initialised with on this page — i.e. the live destination of any newsletter submission.


Troubleshooting

My form submits but I see no [Vendo] Newsletter email captured log

  • The Vendo theme app embed is off. Re-enable it: Online Store > Themes > Customize > App embeds > Vendo: ON > Save.

The log appears but no OneSignal API requests fire in the Network tab

  • The OneSignal SDK didn’t load. Check the OneSignal destination is saved in the Vendo admin (App ID entered, saved). Reload the storefront.
  • Push permission blocked at the browser level — that’s fine for push, but addEmail() should still fire. Check the storefront DevTools console for OneSignal errors.

The user appears in OneSignal but with external_id = <some UUID I don't recognise>

  • That UUID was set by an earlier manual OneSignal.login(...) call on this browser. Vendo doesn’t set arbitrary UUIDs as external_id. To reset for a clean test, run OneSignal.logout() in the storefront’s DevTools console and reload.

The customer-lookup never resolves

  • The email submitted has no matching Shopify customer record yet. Either complete checkout with the same email, or create the customer manually in Shopify admin (Customers > Add customer). Then refresh the storefront — the deferred path will pick it up within a couple of page loads.

Two separate users appear in OneSignal — one keyed by email, one by customer_id

  • Something in the theme is calling OneSignal.login(email). Search the active theme for OneSignal.login( and remove any non-Vendo call. Vendo never calls login() with an email when user_id_type = 'shopify_customer_id'.

Support

If your custom form integration isn’t behaving as expected, enable Debug Mode, reproduce the issue, and email the console output plus the screenshot of the OneSignal Users page (with the Email filter applied) to support@vendodata.com.

Last updated on