PageSourceSearch

https://portal.insiders.live/docs/assets/js/c7cb9c09.61167dd8.js

js insiders.live collected 2026-10-10 01:42:06 UTC 15,961 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkdocs=globalThis.webpackChunkdocs||[]).push([[8163],{327(e,n,i){i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>o,metadata:()=>s,toc:()=>l});const s=JSON.parse('{"id":"integrations/rabbitmq-shovel","title":"RabbitMQ Shovel","description":"Forward device data to your own RabbitMQ broker.","source":"@site/docs/integrations/rabbitmq-shovel.md","sourceDirName":"integrations","slug":"/integrations/rabbitmq-shovel","permalink":"/docs/integrations/rabbitmq-shovel","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":1,"frontMatter":{"sidebar_position":1,"slug":"/integrations/rabbitmq-shovel","title":"RabbitMQ Shovel","description":"Forward device data to your own RabbitMQ broker."},"sidebar":"integrations"}');var r=i(6730),t=i(6451);const o={sidebar_position:1,slug:"/integrations/rabbitmq-shovel",title:"RabbitMQ Shovel",description:"Forward device data to your own RabbitMQ broker."},a="RabbitMQ shovel integration",c={},l=[{value:"How it works",id:"how-it-works",level:2},{value:"Setup",id:"setup",level:2},{value:"Prerequisites",id:"prerequisites",level:3},{value:"Configure via portal UI",id:"configure-via-portal-ui",level:3},{value:"Configure via API",id:"configure-via-api",level:3},{value:"Message format",id:"message-format",level:2},{value:"Routing key",id:"routing-key",level:3},{value:"Message headers",id:"message-headers",level:3},{value:"Body",id:"body",level:3},{value:"Reconfiguring",id:"reconfiguring",level:2},{value:"Monitoring",id:"monitoring",level:2},{value:"Security",id:"security",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"rabbitmq-shovel-integration",children:"RabbitMQ shovel integration"})}),"\n",(0,r.jsx)(n.p,{children:"The RabbitMQ shovel automatically forwards your device data to your own RabbitMQ broker. Once configured, data flows continuously without any polling or WebSocket connections on your side."}),"\n",(0,r.jsx)(n.p,{children:"Data is queued during temporary disconnections and delivered automatically once your consumer reconnects \u2014 no gaps, no backfill logic needed."}),"\n",(0,r.jsx)(n.h2,{id:"how-it-works",children:"How it works"}),"\n",(0,r.jsx)(n.mermaid,{value:"graph LR\n    D[Your Devices] --\x3e P[Insiders platform]\n    P --\x3e S[Shovel]\n    S --\x3e R[Your RabbitMQ broker]\n    R --\x3e A[Your applications]"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Devices send data to the Insiders platform"}),"\n",(0,r.jsx)(n.li,{children:"The platform shovels messages to your configured RabbitMQ exchange"}),"\n",(0,r.jsx)(n.li,{children:"Your applications consume from that exchange via standard AMQP"}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"setup",children:"Setup"}),"\n",(0,r.jsx)(n.h3,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["A RabbitMQ broker accessible from the internet with TLS enabled (",(0,r.jsx)(n.code,{children:"amqps://"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"A TLS certificate signed by a publicly trusted CA (e.g. Let's Encrypt)"}),"\n",(0,r.jsx)(n.li,{children:"A dedicated AMQP user with publish permissions on the target exchange"}),"\n",(0,r.jsx)(n.li,{children:"The target exchange must exist on your broker"}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{title:"Use TLS (amqps://)",type:"warning",children:(0,r.jsxs)(n.p,{children:["Your broker URI should use ",(0,r.jsx)(n.code,{children:"amqps://"})," (port 5671) rather than ",(0,r.jsx)(n.code,{children:"amqp://"})," (port 5672). The shovel transmits data over the public internet \u2014 without TLS, credentials and device data are sent in plaintext. While ",(0,r.jsx)(n.code,{children:"amqp://"})," is technically accepted, ",(0,r.jsxs)(n.strong,{children:["we strongly recommend ",(0,r.jsx)(n.code,{children:"amqps://"})," for all production configurations"]}),"."]})}
1),"\n",(0,r.jsx)(n.admonition,{title:"TLS requires a CA-signed certificate",type:"warning",children:(0,r.jsxs)(n.p,{children:["The broker's TLS certificate ",(0,r.jsx)(n.strong,{children:"must be signed by a publicly trusted Certificate Authority"})," (e.g. Let's Encrypt, DigiCert). The shovel verifies the certificate chain and ",(0,r.jsx)(n.strong,{children:"will not connect"})," to a broker presenting a self-signed or otherwise untrusted certificate."]})}),"\n",(0,r.jsxs)(n.admonition,{title:"Certificate must carry a full, non-wildcard SAN",type:"warning",children:[(0,r.jsxs)(n.p,{children:["The certificate's ",(0,r.jsx)(n.strong,{children:"Subject Alternative Name (SAN)"})," must contain the ",(0,r.jsx)(n.strong,{children:"exact, fully-qualified hostname"})," used in your broker URI (e.g. ",(0,r.jsx)(n.code,{children:"broker.example.com"}),"). Two consequences:"]}),(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"A Common Name (CN) alone is not enough."})," When a SAN is present it is authoritative and the CN is ignored (per RFC 6125). The hostname must appear in the SAN, not just the CN."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Wildcard SANs are not accepted."})," A certificate issued for ",(0,r.jsx)(n.code,{children:"*.example.com"})," fails hostname verification even when connecting to ",(0,r.jsx)(n.code,{children:"broker.example.com"}),"."]}),"\n"]}),(0,r.jsx)(n.p,{children:"On a hostname/SAN mismatch the connection fails \u2014 the shovel shows as configured but no messages are delivered. Issue the broker a certificate whose SAN lists the concrete hostname."})]}),"\n",(0,r.jsx)(n.h3,{id:"configure-via-portal-ui",children:"Configure via portal UI"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["Navigate to ",(0,r.jsx)(n.strong,{children:"Settings > Management"})]}),"\n",(0,r.jsxs)(n.li,{children:["Fill in the connection details:","\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"URL"})," \u2014 your broker's AMQPS address, with the virtual host as the path (e.g. ",(0,r.jsx)(n.code,{children:"amqps://broker.example.com:5671/production"}),"; use ",(0,r.jsx)(n.code,{children:"/%2F"})," for the default ",(0,r.jsx)(n.code,{children:"/"})," vhost). Do not put credentials in the URL."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Username"})," and ",(0,r.jsx)(n.strong,{children:"Password"})," \u2014 AMQP credentials"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Exchange"})," \u2014 the target exchange name"]}),"\n"]}),"\n"]}),"\n",(0,r.jsxs)(n.li,{children:["Click ",(0,r.jsx)(n.strong,{children:"Configure Shovel"})]}),"\n"]}),"\n","\n","\n",(0,r.jsx)(n.h3,{id:"configure-via-api",children:"Configure via API"}),"\n",(0,r.jsxs)(n.p,{children:["Requires ",(0,r.jsx)(n.code,{children:"CUSTOMER_ADMIN"})," role."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'curl -X POST https://portal.insiders.live/api/v2/management/rabbitmq-shovel \\\n  -H "Authorization: ApiKey YOUR_KEY" \\\n  -H "Content-Type: application/json" \\\n  -d \'{\n    "destUri": "amqps://broker.example.com:5671/production",\n    "destUsername": "app-user",\n    "destPassword": "sk_test_not_a_real_password",\n    "destExchange": "device-data"\n  }\'\n'})}),"\n",(0,r.jsxs)(n.p,{children:["A ",(0,r.jsx)(n.code,{children:"destUri"})," that embeds credentials (",(0,r.jsx)(n.code,{children:"amqps://user:pass@host/\u2026"}),"), lacks a host, or does not use the ",(0,r.jsx)(n.code,{children:"amqp"}),"/",(0,r.jsx)(n.code,{children:"amqps"})," scheme is rejected with ",(0,r.jsx)(n.code,{children:"400"}),". See the ",(0,r.jsx)(n.a,{href:"/api/management",children:"management API reference"})," for full request/response details."]}),"\n",(0,r.jsxs)(n.admonition,{title:"Configuration is best-effort \u2014 it does not validate the connection",type:"info",children:[(0,r.jsxs)(n.p,{children:["Configuring the shovel only stores your URI and credentials and registers the shovel \u2014 it does ",(0,r.jsx)(n.strong,{children:"not"})," verify that they are correct. The shovel connects on a best-effort basis: invali
1d credentials, a wrong vhost/exchange, or a TLS failure are ",(0,r.jsx)(n.strong,{children:"not"})," detected or surfaced at configuration time. A shovel reporting ",(0,r.jsx)(n.code,{children:"configured: true"})," means it was set up, ",(0,r.jsx)(n.strong,{children:"not"})," that it is actually connected and working."]}),(0,r.jsxs)(n.p,{children:["The only reliable proof that the shovel works is ",(0,r.jsx)(n.strong,{children:"messages arriving in your exchange"}),". With an active ",(0,r.jsx)(n.a,{href:"/guide/simulators",children:"simulator"})," or a live device producing data, confirm the messages land in your broker. If no messages flow while a simulator or device is active, the configuration is likely invalid (wrong credentials, vhost, exchange, or an untrusted TLS certificate) \u2014 ",(0,r.jsx)(n.a,{href:"https://insiders.live/contact-us/",children:"contact us"}),"."]})]}),"\n",(0,r.jsx)(n.h2,{id:"message-format",children:"Message format"}),"\n",(0,r.jsx)(n.p,{children:"Each data point is published as a separate message to your exchange."}),"\n",(0,r.jsx)(n.h3,{id:"routing-key",children:"Routing key"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"<customerId>.<deviceType>.<deviceId>.<serialNumber>\n"})}),"\n",(0,r.jsx)(n.p,{children:"Example:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"d0836cc0-3281-11f1-9a48.infinity.03fa8120-4258-11f1-b2a5.SN12345\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Use the routing key to filter messages by customer, device type, device ID, or serial number in your exchange bindings. See ",(0,r.jsx)(n.a,{href:"/recommendations/backend-architecture#per-user-device-routing",children:"per-user device routing"})," for a practical example."]}),"\n",(0,r.jsx)(n.h3,{id:"message-headers",children:"Message headers"}),"\n",(0,r.jsx)(n.p,{children:"Each message includes the following AMQP headers:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Header"}),(0,r.jsx)(n.th,{children:"Example"}),(0,r.jsx)(n.th,{children:"Description"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"x-device-id"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"03fa8120-4258-11f1-b2a5"})}),(0,r.jsx)(n.td,{children:"Device UUID"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"x-device-type"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"infinity"})}),(0,r.jsxs)(n.td,{children:["Device profile type (e.g. ",(0,r.jsx)(n.code,{children:"infinity"}),", ",(0,r.jsx)(n.code,{children:"intense"}),")"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"x-serial-number"})}),(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"SN12345"})}),(0,r.jsx)(n.td,{children:"Device serial number"})]})]})]}),"\n",(0,r.jsx)(n.h3,{id:"body",children:"Body"}),"\n",(0,r.jsx)(n.p,{children:"The message body is a JSON array of timestamped data points. Multiple keys and timestamps can be batched in a single message:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'[\n  {"ts": 1778074376430, "values": {"lon": 6.6406662, "lat": 46.5516617, "alt": 502.574}},\n  {"ts": 1778074376530, "values": {"lon": 6.6408354, "lat": 46.551575, "alt": 502.608}},\n  {"ts": 1778074376630, "values": {"lon": 6.6410225, "lat": 46.5514787, "alt": 502.641}},\n  {"ts": 1778074376684, "values": {"batLvl": 46.22}},\n  {"ts": 1778074376700, "values": {"hrsHr": 142}}\n]\n'})}),"\n",(0,r.jsx)(n.p,{children:"Each entry contains:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Field"}),(0,r.jsx)(n.th,{children:"Type"}),(0,r.jsx)(n.th,{children:"Description"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"ts"})}),(0,r.jsx)(n.td,{children:"number"}),(0,r.jsx)(n.td,{children:"Timestamp in Unix milliseconds"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"values"})}),(0,r.jsx)(n.td,{children:"object"}),(0,r.jsx)(n.td,{children:"Key-value pairs of data points at that timestamp"})]})]})]}),"\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsx)(n.a,{href:"/guide/timeseries-data",children:"Timeseries data format"})," for details on the data shape, value types, and handling missing data points (e.g. GNSS fixes)."]}),"\n",(0,r.jsx)(n.h2,{id:"reconfiguring",children:"Reconfiguring"}),"\n",(0,r.jsx)(n.p,{children:"To change the shovel configuration:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Delete the existing configuration"}),"\n",(0,r.jsx)(n.li,{children:"Create a new configuration with the updated details"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"The shovel cannot be updated in-place \u2014 delete and recreate."}),"\n",(0,r.jsx)(n.h2,{id:"monitoring",children:"Monitoring"}),"\n",(0,r.jsx)(n.p,{children:"Check shovel status:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:'curl -H "Authorization: ApiKey YOUR_KEY" \\\n  https://portal.insiders.live/api/v2/management/rabbitmq-shovel\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"configured: true"})," only means the shovel was set up \u2014 it does ",(0,r.jsx)(n.strong,{children:"not"})," confirm a working connection (see the note in ",(0,r.jsx)(n.a,{href:"#configure-via-api",children:"Configure via API"}),"). The real test is whether messages reach your exchange. If you're not receiving messages while a simulator or device is active:"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Verify your broker is reachable from the internet"}),"\n",(0,r.jsx)(n.li,{children:"Confirm the TLS certificate is signed by a publicly trusted CA (self-signed certificates are rejected)"}),"\n",(0,r.jsxs)(n.li,{children:["Confirm the certificate's SAN lists the exact broker hostname \u2014 a CN-only or wildcard (",(0,r.jsx)(n.code,{children:"*.example.com"}),") certificate fails hostname verification"]}),"\n",(0,r.jsx)(n.li,{children:"Check that the exchange exists and the user has publish permissions"}),"\n",(0,r.jsx)(n.li,{children:"Verify the credentials and vhost are correct"}),"\n",(0,r.jsx)(n.li,{children:"Check your broker's connection logs for rejected connections"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["If messages still don't flow, ",(0,r.jsx)(n.a,{href:"https://insiders.live/contact-us/",children:"contact us"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"security",children:"Security"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Credentials are transmitted over TLS and stored encrypted on the platform"}),"\n",(0,r.jsx)(n.li,{children:"The API never returns your password after initial configuration"}),"\n",(0,r.jsxs)(n.li,{children:["Only ",(0,r.jsx)(n.code,{children:"CUSTOMER_ADMIN"})," users can configure or delete the shovel"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},6451(e,n,i){i.d(n,{R:()=>o,x:()=>a});var s=i(162);const r={},t=s.createContext(r);function o(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.