---
title: "WooCommerce MailerLite Integration: Plugin or API Upsert"
description: "WooCommerce to MailerLite: what the official plugin covers, the API upsert that never removes groups, consent at checkout, and the 120-a-minute limit."
url: "https://wpwebhooks.org/blog/woocommerce-mailerlite-integration/"
date: "2026-10-20"
---

# WooCommerce MailerLite Integration: Plugin or API Upsert

**TL;DR:** For most stores the official MailerLite plugin is the right answer. Go to the API when MailerLite is one destination among several, or when the rules about who lands in which group are yours.

-   `POST https://connect.mailerlite.com/api/subscribers` is an **upsert**: 201 when it creates, 200 when it updates. Fields and groups you leave out are never removed.
-   `PUT /api/subscribers/:id` is not the same call. It **removes the subscriber from every group you do not list**.
-   Consent is your job. A checkbox registered with WooCommerce's additional checkout fields API is stored on the order, and the code that reads it decides whether anything is sent at all.
-   The limit is **120 requests a minute** per account, with a `Retry-After` header on the 429.

/ Official plugin

## Is there an official MailerLite plugin for WooCommerce?

Yes, and it is good. [MailerLite – WooCommerce integration](https://wordpress.org/plugins/woo-mailerlite/) has 30,000+ active installs.¹ It adds a subscribe checkbox to checkout with a choice of position and label, a double opt-in switch, order data forwarded to MailerLite, product import, order-related custom fields and segments, sales tracking for campaign ROI, automations triggered by recent purchases, and abandoned cart emails.

That list covers what most shops want. If yours is one of them, install it and stop reading. Abandoned carts in particular are not something you should rebuild yourself.

The API is for the cases the plugin was not built around. The same customer also has to reach a CRM, a Slack channel or an n8n workflow, and you want one place where every delivery is logged. Group membership depends on rules that are yours: which product was bought, whether it was a renewal, which shipping country. Or the event is not a checkout at all, such as a membership activation or a course enrolment.

/ Authentication

## How do you authenticate to the MailerLite API?

With an API key sent as a Bearer token. In MailerLite, open _Integrations_, choose _MailerLite API_ and click _Generate new token_. MailerLite shows the key once and stores it hashed, so copy it straight into wherever your site keeps secrets.

Two details from the [getting-started page](https://developers.mailerlite.com/getting-started) matter in production. Every request sends `Content-Type: application/json` and `Accept: application/json` to the base URL `https://connect.mailerlite.com/api`. And the key is **bound to the user who created it**: remove that person from the account and the key stops authenticating, and every request answers `401 Unauthenticated`. Create it from an account owner who is not about to leave, and name it after the site.

Requests use the latest API version unless you pin one. Send `X-Version` with the date you implemented the integration and a later change to the API cannot alter what your requests do.

/ Upsert

## Which endpoint adds a WooCommerce customer without creating duplicates?

`POST /api/subscribers`. The [subscribers reference](https://developers.mailerlite.com/api/subscribers) calls it _create/upsert_: if the email exists the subscriber is updated, and it is a non-destructive operation, so omitting fields or groups does not remove them. Only `email` is required. The response code tells you which case happened: **201 Created** for a new subscriber, **200 OK** for an existing one.

JSON — body for POST /api/subscribers

```
{
  "email": "ada@example.com",
  "fields": {
    "name": "Ada",
    "last_name": "Lovelace"
  },
  "groups": ["4243829086487936"]
}
```

Three rules decide whether that body works:

-   **`groups` holds group ids, not names**, and the groups must already exist. Read the ids from `GET /api/groups` once and keep them in configuration.
-   **Every key in `fields` must be a default or custom field.** `name` and `last_name` exist on every account. Anything else, such as `last_order_total`, has to be created first with [`POST /api/fields`](https://developers.mailerlite.com/api/fields), which takes a `name` and a `type` of `text`, `number` or `date`.
-   **Do not switch to PUT for updates.** `PUT /api/subscribers/:id` looks like the update half of the API, but the same reference says the subscriber "will be removed from unlisted groups". A PUT that sends only the customer group silently takes the person out of every newsletter group they joined from a form.

FIG 01 — Consent decides whether an event exists at all; the upsert only adds fields and groups

/ Consent

## How do you record newsletter consent at WooCommerce checkout?

WooCommerce has no marketing checkbox of its own, and the MailerLite API accepts any address you send it. Whether the customer agreed is a fact only your checkout knows. Add the checkbox with the [additional checkout fields API](https://developer.woocommerce.com/docs/block-development/extensible-blocks/cart-and-checkout-blocks/additional-checkout-fields/), which stores the answer on the order. Then fire your own action only when the box was ticked:

PHP — checkout checkbox and an opt-in action

```
add_action( 'woocommerce_init', function () {
    woocommerce_register_additional_checkout_field( array(
        'id'       => 'my-shop/newsletter',
        'label'    => 'Email me news and offers',
        'location' => 'order',
        'type'     => 'checkbox',
    ) );
} );

add_action( 'woocommerce_order_status_processing', function ( $order_id, $order ) {
    // Order-location fields are saved under the _wc_other/ prefix; a ticked box is "1".
    if ( '1' === $order->get_meta( '_wc_other/my-shop/newsletter' ) ) {
        do_action( 'my_shop_newsletter_optin', $order );
    }
}, 10, 2 );
```

Three things to know about that code. The field API renders in the **Checkout block only**, not the old `[woocommerce_checkout]` shortcode. WooCommerce's docs recommend reading values through their `CheckoutFields` helper and document the `_wc_other/` meta key as the direct route, with a ticked checkbox stored as `"1"`. And `woocommerce_order_status_processing` fires again if an order goes back to processing after being on hold. That is harmless here, because the upsert is idempotent: the same email lands on the same subscriber.

Gating the event, rather than sending everyone and filtering later, means a customer who did not tick the box never reaches MailerLite at all. No log, no queue entry, no subscriber record to clean up.

/ Unsubscribed

## What happens when a customer who unsubscribed orders again?

They stay unsubscribed. The upsert updates their fields and groups but does not change the status back to active. The endpoint does take a `resubscribe` flag, documented as "set to true to resubscribe previously unsubscribed subscribers", and that is the point where a careless integration does damage.

Ticking a box at checkout is fresh consent, so setting `resubscribe` for an order with the box ticked is defensible. Setting it on every order is not: a customer who unsubscribed in March and bought again in October without ticking anything would be back on the list. The PUT endpoint is stricter still. For an unsubscribed, bounced or junk subscriber, the reference says reactivation is only possible through the app, a form or a landing page.

/ Rate limits

## What are the MailerLite API rate limits?

A global limit of **120 requests per minute**. Past it you get `429 Too Many Attempts` with `X-RateLimit-Remaining: 0` and a `Retry-After` header in seconds. Imports have a separate limit of **5 requests per minute**, and that includes batch requests made up entirely of subscriber upserts.

Checked against real store volume, with one upsert per opted-in order:

-   A Black Friday hour of 600 orders is 600 ÷ 60 = **10 requests a minute**, a twelfth of the limit.
-   Backfilling 20,000 existing customers one by one is 20,000 ÷ 120 = **167 minutes** at the very best. Use the import endpoint instead.

So the live integration will not hit the limit. A backfill sharing the same key with it will, and the live orders arriving during the backfill are the ones that get the 429s.

| Concern | Hand-rolled wp\_remote\_post | Webhook Actions |
| --- | --- | --- |
| Abandoned carts, product sync, campaign ROI | Not this. Use the official plugin | Not this either. It delivers events; it has no MailerLite connector and no cart tracking |
| Which events | One add\_action per event, written by you | Any WordPress action as a trigger, including your own my\_shop\_newsletter\_optin |
| Where the call runs | Inside the status change unless you build a queue | Queued in its own table, delivered in the background |
| The API key | A constant in wp-config.php | A Bearer credential in the Credentials Vault, redacted in every log |
| email, name, last\_name | Built in your array | Field mapping from args.0.billing into email, fields.name and fields.last\_name |
| Group ids | Written in your array | A short pre-dispatch snippet; field mapping moves values and cannot invent one |
| A 429 or 5xx | Lost unless you wrote retry logic | Retried with exponential backoff, five attempts by default. Retry-After is not read |
| A 422 from an unknown field | Invisible | Marked permanently failed with MailerLite's errors object in the delivery log, replayable after the fix |

The group rules are a few lines of PHP. Field mapping has already produced `email` and the two name fields, and passed the order's line items through as `items`; the snippet turns products into group ids:

PHP — pre-dispatch snippet that sets the groups

```
// $payload arrives field-mapped: email, fields.name, fields.last_name, items.
$groups     = [ '4243829086487936' ];               // everyone who opted in
$by_product = [ 412 => '31985378335392975' ];       // product id => group id

foreach ( $payload['items'] ?? [] as $item ) {
    if ( isset( $by_product[ $item['product_id'] ] ) ) {
        $groups[] = $by_product[ $item['product_id'] ];
    }
}

$payload['groups'] = array_values( array_unique( $groups ) );
unset( $payload['items'] );                         // MailerLite does not need them
return $payload;
```

try\_it

Seeing it run beats reading about it. The live preview boots a throwaway WordPress with Webhook Actions already installed and demo deliveries sitting in the log — no signup, nothing left on your machine afterwards.

[Try the live preview →](https://playground.wordpress.net/?blueprint-url=https://wpwebhooks.org/blueprint.json) [Install plugin](https://downloads.wordpress.org/plugin/flowsystems-webhook-actions.zip)

/ Failures

## Which MailerLite errors should be retried?

Only 429 and 5xx. A 429 clears by itself. With retries 60 and then a further 120 seconds apart, the second retry lands 60 + 120 = 180 seconds after the first attempt, past even the 119-second `Retry-After` in MailerLite's own example, without the header ever being read. A 5xx is on MailerLite's side and is usually brief.

Everything in the 4xx range is a request that will fail the same way next time. A **401** means the key was revoked or its user was removed. A **422** carries an `errors` object naming the field, most often an invalid email or a `fields` key that does not exist on the account. A **403** means the action is denied for that account or token. Retrying those burns requests from the same 120-a-minute budget the healthy orders need. Log them where someone will look, which is the whole subject of [why webhooks fail silently](https://wpwebhooks.org/blog/why-wordpress-webhooks-silently-fail-in-production/).

/ Exposure

## What does MailerLite not protect you from?

**Your own consent logic.** MailerLite's terms put it directly: when you collect subscribers through your own API key, you are responsible for preventing abuse. A checkout that adds everyone is a checkout that sends campaigns to people who never asked for them, and spam complaints count against the account's sending reputation.

**The bill.** [MailerLite plans are priced by subscriber count](https://www.mailerlite.com/pricing). Every guest checkout you push in moves you toward the next tier, whether or not that person ever opens an email. Opt-in gating keeps the list and the invoice the same size as your actual audience.

**A key that dies with a person.** Because the key is bound to its creator, an offboarding in the marketing team can stop the integration with no change on your side. The symptom is a run of 401s, which is why a 401 should alert rather than retry.

If the same store also feeds Mailchimp or Klaviyo, the [Mailchimp](https://wpwebhooks.org/blog/woocommerce-mailchimp-integration/) and [Klaviyo](https://wpwebhooks.org/blog/woocommerce-klaviyo-integration/) articles cover their APIs, which handle status and tags quite differently.

/Footnotes

¹ MailerLite – WooCommerce integration on [WordPress.org](https://wordpress.org/plugins/woo-mailerlite/), read 2026-10-22: version 3.1.28, 30,000+ active installations. Feature list from the plugin description.

² Endpoint behaviour, status codes and rate limits from the [MailerLite API documentation](https://developers.mailerlite.com/getting-started), read 2026-10-22.

## Structured data

```json
{"@context":"https://schema.org","@type":"Article","headline":"WooCommerce MailerLite Integration: Plugin or API Upsert","description":"WooCommerce to MailerLite: what the official plugin covers, the API upsert that never removes groups, consent at checkout, and the 120-a-minute limit.","datePublished":"2026-10-20","dateModified":"2026-10-20","author":{"@type":"Person","name":"Mateusz Skorupa","url":"https://wpwebhooks.org/about/"},"publisher":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org"},"url":"https://wpwebhooks.org/blog/woocommerce-mailerlite-integration/","image":"https://wpwebhooks.org/og_image.jpg","keywords":["mailerlite woocommerce","woocommerce mailerlite","woocommerce mailerlite integration","mailerlite wordpress plugin","mailerlite api","mailerlite api add subscriber"]}

{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"WP Webhooks","item":"https://wpwebhooks.org/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://wpwebhooks.org/blog/"},{"@type":"ListItem","position":3,"name":"WooCommerce MailerLite Integration: Plugin or API Upsert","item":"https://wpwebhooks.org/blog/woocommerce-mailerlite-integration/"}]}

{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[{"@type":"Question","name":"Is there an official MailerLite plugin for WooCommerce?","acceptedAnswer":{"@type":"Answer","text":"Yes. MailerLite - WooCommerce integration has 30,000+ active installs. It adds a subscribe checkbox at checkout with a double opt-in switch, forwards order data, imports products, tracks sales for campaign ROI and sends abandoned cart emails. Use the API when the group rules are your own or MailerLite is one of several destinations."}},{"@type":"Question","name":"How do I add a subscriber with the MailerLite API without creating duplicates?","acceptedAnswer":{"@type":"Answer","text":"Send POST https://connect.mailerlite.com/api/subscribers with a Bearer API key. It is an upsert: 201 Created for a new email, 200 OK for an existing one. It is non-destructive, so fields and groups you leave out are not removed from the subscriber."}},{"@type":"Question","name":"Why did a MailerLite subscriber disappear from their groups after an update?","acceptedAnswer":{"@type":"Answer","text":"Because the update was a PUT to /api/subscribers/:id. Unlike the POST upsert, PUT removes the subscriber from every group not listed in the request. Use the POST upsert to add groups, or POST /api/subscribers/{subscriber_id}/groups/{group_id} to assign one group."}},{"@type":"Question","name":"Can the MailerLite API resubscribe someone who unsubscribed?","acceptedAnswer":{"@type":"Answer","text":"Only if you ask. The upsert leaves an unsubscribed subscriber unsubscribed unless you send resubscribe set to true. Send it only when the customer gave fresh consent, such as ticking a newsletter box at checkout, never for every new order."}},{"@type":"Question","name":"What is the MailerLite API rate limit?","acceptedAnswer":{"@type":"Answer","text":"A global limit of 120 requests per minute, answered with 429 and a Retry-After header when exceeded. Imports, including batch requests made entirely of subscriber upserts, have a separate limit of 5 requests per minute."}}]}

{"@context":"https://schema.org","@type":"ImageObject","contentUrl":"https://wpwebhooks.org/diagrams/woocommerce-mailerlite-integration.png","caption":"FIG 01 — Consent decides whether an event exists at all; the upsert only adds fields and groups","description":"An order reaches processing. A checkout checkbox stored on the order decides whether a custom opt-in action fires; without consent nothing is queued. The queued event is mapped to email, name and last name, a snippet adds the group id, and the worker sends POST /api/subscribers to connect.mailerlite.com with a Bearer API key. 201 means created, 200 means updated. Omitted fields and groups are left alone. A 429 or 5xx is retried; 401 and 422 are not. An unsubscribed address stays unsubscribed unless resubscribe is sent.","encodingFormat":"image/png","creator":{"@type":"Organization","name":"WP Webhooks","url":"https://wpwebhooks.org/"},"copyrightHolder":{"@type":"Organization","name":"Flow Systems","url":"https://flowsystems.pl/"},"copyrightNotice":"© Flow Systems","creditText":"WP Webhooks","license":"https://creativecommons.org/licenses/by/4.0/","acquireLicensePage":"https://wpwebhooks.org/image-license/"}
```
