API Response Warnings

Sometimes an API request succeeds, but the outcome differs from what you asked for: a parameter was ignored, an optional flow was skipped, or a record was changed in a way you may not have intended. Instead of failing the request or staying silent, the Broadcast API explains the difference in a warnings array included in the response.

This is a cross-cutting convention: any endpoint that accepts input may include warnings in its response.

When warnings appear

A warning is added whenever the API accepts your request but the result diverges from the input. The most common situations:

  1. A parameter was not recognized: for example, a misspelled field, or a parameter placed at the wrong nesting level. The API processes the rest of the request and tells you which parameters it ignored. This applies at every depth: a key inside a settings object, a segment rule or an email header, an entry of the wrong type in a list such as segment_ids, and a key inside a block document. The message lists the keys that are recognized at that level.
  2. A requested flow was skipped: for example, requesting double opt-in for a subscriber who is already confirmed. The request succeeds, but no confirmation email is sent.
  3. A side effect you may not have intended: for example, creating a subscriber that already exists reactivates the existing record.
  4. A webhook your request triggered had a problem: the change is saved and the request succeeds, but a webhook it triggered couldn’t be recorded, was held, or is waiting on an endpoint that isn’t responding. See Webhook warnings.
  5. A better-suited endpoint exists for what you appear to be doing: for example, updating a subscriber with is_active: false deactivates the record, but only the dedicated unsubscribe endpoint records an unsubscribe date and adds the address to the suppression list.
  6. Another setting decided the value: for example, a block email must have an unsubscribe link, so html_body_add_footer: false sent with a document that has no footer block and no {{ unsubscribe_url }} link is set back to true. See The footer of an email.
  7. Content was changed before it was stored or sent: for example, a text block keeps only simple formatting, so a style attribute on a link is removed. See Block documents.

Warnings apply to update endpoints exactly as they do to create endpoints: an unrecognized or misplaced parameter in a PATCH/PUT request is ignored and reported the same way.

If everything in your request was applied exactly as sent, the warnings key is absent from the response.

Response format

Warnings appear as a top-level warnings array alongside the normal response body. Each warning has a machine-readable code, a human-readable message, and, where relevant, the param it refers to:

{
  "id": 123,
  "email": "jane@example.com",
  "is_active": true,
  "confirmed_at": "2026-01-10T09:30:00Z",
  "warnings": [
    {
      "code": "double_opt_in_skipped",
      "message": "Subscriber is already confirmed; no confirmation email was sent."
    }
  ]
}

A warning never changes the HTTP status code. A 200/201 with warnings is still a success: the warnings only describe how the outcome differs from the request.

Warnings appear only on successful (2xx) responses. A failed request performs no work, so there is no divergence to report: the error/errors key is the explanation.

A small number of endpoints respond with an empty body (for example, adding or removing a sequence subscriber); those responses cannot carry a warnings array, but webhook problems still reach you through the Broadcast-Warning header (see Webhook warnings).

List endpoints can warn too: an unusable filter value (for example, a created_after date that cannot be parsed) is reported with parameter_ignored while the rest of the query is answered normally.

Warnings describe what happened during this request. Retrying an identical request may return different warnings (or none) because the first request changed state. For example, a create that reactivates an inactive subscriber warns subscriber_reactivated; the identical retry finds an already-active subscriber and raises no warning.

Warning codes

Code Meaning
unrecognized_parameter A parameter in your request was not recognized and was ignored. The param field names it. Check the spelling and nesting level against the endpoint’s documentation.
double_opt_in_skipped Double opt-in was requested, but the subscriber is already confirmed, so the confirmation step was skipped. On subscriber creation no confirmation email is sent; on transactional sends the email is sent normally without a confirmation step.
subscriber_reactivated The create call matched an existing inactive or unsubscribed subscriber, and that record was reactivated.
subscriber_suppressed The create or resubscribe call resubscribed a subscriber whose address is on a suppression list, so the subscriber stays inactive. Remove the address from the suppression list first, then activate the subscriber.
use_dedicated_endpoint The update was applied, but a dedicated endpoint exists for this operation with additional behavior you likely want. The message names it.
parameter_ignored A recognized parameter was not applied in this context: for example, a value only used by another flow, a field that cannot change in the record’s current state, or a filter value that could not be interpreted. The message explains why.
webhook_not_recorded Your change was saved, but a webhook it triggered could not be recorded and will not be delivered. The app logs have the details.
webhook_held Your change was saved, but a webhook it triggered could not be queued. It is held and will be sent later.
parameter_overridden The server applied a different value than the one you sent: for example, creating a subscriber always activates it, even if you pass is_active: false. The response body shows the value actually applied.
provider_setup_failed Your change was saved, but Broadcast could not apply it at the email provider (for example, the Postmark webhook for postmark_subscription_sync). The message has the provider’s error. Try the change again later, or check the provider account.
content_sanitized HTML you sent in a block document will be changed when the email is rendered: tags, attributes or links that the block does not allow are removed. The message names what is removed, never the content itself.

The set of codes will grow over time as more endpoints adopt richer warnings. Treat unknown codes as informational: log them, but do not fail on them.

Stability guarantees

  • code values are stable. Codes are append-only: new codes may be added, but existing codes are never renamed or removed. Branch your logic on code.
  • message text is not stable. Messages are written for humans and may be reworded at any time; never parse or match on them.
  • param uses dot-path notation relative to the request body or query string, e.g. subscriber.double_opt_in, including keys inside option hashes, e.g. double_opt_in.replyto for a misspelled reply_to. A position in a list is a number: segment.segment_groups_attributes.0.segment_rules_attributes.1.negate, or broadcast.blocks_json.blocks.2.styles.bg. It names the parameter only; submitted values are never echoed back.
  • Tolerate the unknown. Your client must accept responses with no warnings key, warnings with codes it has not seen, and warnings with additional fields added later.

Example: misplaced parameter

A common mistake is nesting flow-level options inside the subscriber object. Flow options such as double_opt_in and confirmation_template_id are top-level parameters (see Subscribers API):

POST /api/v1/subscribers

{
  "subscriber": {
    "email": "jane@example.com",
    "double_opt_in": true
  }
}

The subscriber is created, but double_opt_in is not a subscriber attribute, so no confirmation flow runs. The response makes that visible:

{
  "id": 124,
  "email": "jane@example.com",
  "warnings": [
    {
      "code": "unrecognized_parameter",
      "param": "subscriber.double_opt_in",
      "message": "double_opt_in is not a subscriber attribute and was ignored. Pass it as a top-level parameter instead."
    }
  ]
}

Example: deactivating instead of unsubscribing

Update endpoints raise warnings the same way. Here, setting is_active: false via the generic update succeeds, but unsubscribing through the dedicated endpoint is almost always what you want: it also records the unsubscribe date and adds the address to the channel’s suppression list:

PATCH /api/v1/subscribers?email=jane@example.com

{
  "subscriber": {
    "is_active": false
  }
}
{
  "id": 123,
  "email": "jane@example.com",
  "is_active": false,
  "unsubscribed_at": null,
  "warnings": [
    {
      "code": "use_dedicated_endpoint",
      "param": "subscriber.is_active",
      "message": "Subscriber was deactivated, but no unsubscribe date or suppression entry was recorded. To unsubscribe a subscriber, use POST /api/v1/subscribers/unsubscribe instead."
    }
  ]
}

Example: a topic the subscriber turned off

2.49+

Only the subscriber can turn on a topic they turned off. Here, newsletter is a tag and webinars is the tag of a topic the subscriber turned off from the unsubscribe link:

POST /api/v1/subscribers/add_tag

{
  "email": "jane@example.com",
  "tags": ["newsletter", "webinars"]
}

newsletter is added and webinars is not. The rest of the request is applied, and the warning names the topic:

{
  "id": 123,
  "email": "jane@example.com",
  "tags": ["newsletter"],
  "warnings": [
    {
      "code": "parameter_ignored",
      "param": "tags",
      "message": "Webinars was turned off by the subscriber. Only the subscriber can turn it back on, from their preference page."
    }
  ]
}

Create and update do the same for subscriber.tags, and for a custom data topic in subscriber.custom_data. See Topics the subscriber turned off.

Block documents

A block document (content_type: "blocks" with blocks_json) is checked when you create or update a broadcast, template, sequence step or transactional email. The document is always saved as you sent it; the warnings tell you what the renderer will ignore or change:

What you sent Code
A key that the block type, theme or meta does not know, such as styles.bg on a text block unrecognized_parameter
A block type that is not supported. The block is kept but does not render. unrecognized_parameter
A colour the renderer does not accept (it accepts hex values such as #1a73e8, transparent and colour names). The default colour is used instead; for styles.linkColor the theme link colour is used. parameter_ignored
A link the renderer does not accept (it accepts https, http, mailto, tel, #anchors and Liquid tags such as {{ unsubscribe_url }}) parameter_ignored
HTML that a text, image + text or footer block does not allow. These blocks keep p, br, strong, em, u, ul, ol, li and links with an href. b and i become strong and em. content_sanitized
Scripting or unsupported markup in an html block, such as script tags or onclick attributes content_sanitized
html_body_add_footer that does not match the document parameter_overridden
body sent together with a document. The body is rendered from the document. parameter_overridden

To colour the links in a text or image + text block, set styles.linkColor on the block, not a style attribute on the link. A link takes the first colour that is set:

  1. The styles.linkColor value of the block (Broadcast 2.48.0 or later).
  2. The theme.link value of the document.
  3. The link colour of the brand kit.

Use an html block for markup the other blocks do not allow.

POST /api/v1/broadcasts

{
  "broadcast": {
    "subject": "October news",
    "content_type": "blocks",
    "blocks_json": {
      "version": 1,
      "blocks": [
        {
          "id": "intro",
          "type": "text",
          "props": { "html": "<p>Read <a href=\"https://example.com\" style=\"color:#c00\">the post</a></p>" },
          "styles": { "bg": "#fff8e1" }
        }
      ]
    }
  }
}
{
  "id": 185,
  "warnings": [
    {
      "code": "unrecognized_parameter",
      "param": "broadcast.blocks_json.blocks.0.styles.bg",
      "message": "bg is not a recognized key in styles for a text block and was ignored. Recognized keys: align, color, linkColor, size, lineHeight, padding."
    },
    {
      "code": "content_sanitized",
      "param": "broadcast.blocks_json.blocks.0.props.html",
      "message": "A text block keeps only p, br, strong, em, u, ul, ol, li and links (a with href), and was changed. Removed: style attributes. Use an html block for custom markup. Links take the linkColor style of a text or imageText block, else the theme.link colour of the document, or the brand kit link colour when it has none."
    }
  ]
}

Every marketing email has one unsubscribe link. Broadcast adds the channel footer only when the email has no unsubscribe link of its own. From Broadcast 2.46.0, the rule is the same for rich text, HTML and block emails:

  1. If the email has its own unsubscribe link, the channel footer is not added, whatever html_body_add_footer says. An own link is a footer block that shows its unsubscribe link, or a link to {{ unsubscribe_url }} that every subscriber sees.
  2. If a rich-text or block email has no own link, the channel footer is added.
  3. If an HTML email has no own link, html_body_add_footer decides (default true).

A link counts only when every subscriber sees it. A link inside a Liquid tag such as {% if %}, in a block with show_if_tag, or in a column block with show_if_tag, does not count. A footer block with Show unsubscribe link off ("showUnsubscribe": false) does not count either.

If you send a value that does not apply, the response carries a parameter_overridden warning. For example, html_body_add_footer: false on a block email with no footer block and no {{ unsubscribe_url }} link is set to true, and html_body_add_footer: true on an email with its own link is not applied.

Emails sent with the unsubscribe link off: the send decides, not the template. An email sent with its unsubscribe link off gets no channel footer, no unsubscribe link and no List-Unsubscribe header, whatever html_body_add_footer says. A footer block in it shows no unsubscribe link. html_body_add_footer applies only to an email sent with the link on, so a stored true does not mean that every send gets the channel footer. Where to switch the link off:

  • Confirmation from an opt-in form: turn off the form’s unsubscribe link setting (automation_settings.include_unsubscribe_link_in_confirmation: false). It is on by default.
  • Confirmation from the subscribers API: send double_opt_in: { include_unsubscribe_link: false }. Without it, the link is on. The template must be a confirmation template (template_purpose: "confirmation"); any other template is refused with a 422.
  • Sequence step: set the step’s include_unsubscribe_link: false. It is on by default, and it decides for a step that uses a template too.
  • Transactional email: the link is off by default. It is on only when you send include_unsubscribe_link: true.

A block email whose footer block has Show unsubscribe link off, such as a double opt-in confirmation, gets a parameter_overridden warning on html_body_add_footer that lists these settings. The warning for a confirmation template says this too.

POST /api/v1/broadcasts

{
  "broadcast": {
    "subject": "October news",
    "content_type": "blocks",
    "html_body_add_footer": false,
    "blocks_json": {
      "version": 1,
      "blocks": [
        { "id": "body", "type": "text", "props": { "html": "<p>Hello</p>" } },
        {
          "id": "our-footer",
          "type": "html",
          "props": { "html": "<p>Example GmbH, Musterstraße 1, Berlin. <a href=\"{{ unsubscribe_url }}\">Unsubscribe</a></p>" }
        }
      ]
    }
  }
}

This broadcast goes out with your footer only, and the response carries no warning.

Errors, not warnings

A value that the API cannot store at all is an error, not a warning. For example, an unknown value for a field with a fixed list of values, such as vendor, content_type or a sequence step action, returns 422 Unprocessable Content. The message names the parameter and lists the accepted values, and nothing is saved.

Webhook warnings

A change made through the API is never rejected because its webhook couldn’t be sent. If a webhook the request triggered had a problem, the request still succeeds with a 2xx status and tells you:

  • JSON object responses include the problem in the warnings array, with one of the webhook_* codes above.
  • Every successful response also carries a Broadcast-Warning header, including responses with an empty body such as adding a subscriber to a sequence. Several warnings are separated by commas.
HTTP/1.1 200 OK
Broadcast-Warning: webhook_held; event="subscriber.updated"
{
  "id": 123,
  "email": "jane@example.com",
  "warnings": [
    {
      "code": "webhook_held",
      "message": "Your change was saved, but the subscriber.updated webhook could not be queued. It is held and will be sent later."
    }
  ]
}

Requests that commonly trigger webhooks, such as creating or updating a subscriber or adding one to a sequence, can carry these warnings. When nothing went wrong with the webhook, neither the warning nor the header is added. A webhook that is held only because your endpoint isn’t responding doesn’t produce a warning either: the event is recorded and will be delivered. Endpoint outages are shown on the Webhooks page instead (see Webhooks).

Handling warnings in your integration

  • Log them. Warnings are the fastest way to spot an integration bug: a typo’d field name or misplaced parameter shows up immediately instead of failing silently.
  • Do not treat them as errors. The request succeeded, and the response body reflects the actual state. Warnings are advisory context, not a signal to retry.
  • Ignore unknown keys and codes. Your client should tolerate both the warnings key being absent and warning codes it has not seen before.

Backwards compatibility

The warnings array is additive and optional:

  • No existing response fields change: names, types, and values stay the same.
  • HTTP status codes are unchanged.
  • The key is only present when at least one warning was raised.

Clients that read specific fields from responses (the common case, including the official Ruby SDK) are unaffected. If you validate responses against a strict schema, allow additional properties, per standard REST evolution rules, the API may add new response fields over time.

Last updated

Was this page helpful?

Thanks for your feedback!

Thanks for letting us know. We'll work on improving this page.