All developer tools

BLE Advertisement Decoder

Decode hex or Base64 BLE advertising payloads into AD structures, manufacturer data, service data, company identifiers, and recognized beacon formats.

Examples:

Build the scanner behind the data

Use SimpleBLE to scan consistently across supported platforms and surface the same advertising fields in your application.

View scanning examples
Background

What a BLE advertisement actually is

Before a connection exists, a BLE peripheral has one job: be found. It does that by transmitting short advertising packets on the three primary advertising channels (37, 38, and 39). A central — phone, laptop, gateway — scans those channels, reads the payload, and decides whether to connect, filter the device out, or ignore it.

That payload is not free-form. The Bluetooth Core Specification defines a tight layout of AD structures: typed chunks that carry flags, names, service UUIDs, manufacturer blobs, and more. If you are staring at a hex dump from nRF Connect, Wireshark, an Android btsnoop log, or your own scanner, you are looking at exactly those structures. This tool splits them apart and labels each field.

Advertising is deliberately small on the legacy path: a classic advertising PDU carries at most 31 bytes of payload. That is why firmware authors pack data carefully, drop the local name in production, and split content across an advertising packet and a scan response. BLE 5 extended advertising changes how large payloads get on air (secondary channels, chaining) but not the AD structure format inside the data — the same length/type/data walk still applies. Details are below.

Format

Anatomy of an advertising payload

Every advertising payload is a sequence of AD structures back to back. Each structure is three pieces:

  1. Length (1 byte) — number of bytes that follow, covering type + data. Not the size of the data alone.
  2. AD Type (1 byte) — what the data means. Assigned by the Bluetooth SIG (0x01 flags, 0xFF manufacturer data, and so on).
  3. Data (length − 1 bytes) — the value for that type.

Parsing is a walk: read length, read type, consume length − 1 data bytes, repeat until you hit the end or a zero length (padding). If length claims more bytes than remain, the packet is truncated or corrupt — the tool flags that.

Worked example — Polar H10 style wearable
02 01 06  05 03 0D 18 0F 18  0A 09 50 6F 6C 61 72 20 48 31 30  02 0A 04

Four AD structures: Flags → Heart Rate + Battery service UUIDs → local name “Polar H10” → TX power +4 dBm. Load the Wearable example above to see the same bytes decoded live.

Reading it by hand:

  • 02 01 06 — length 2, type Flags (0x01), value 0x06 = General Discoverable + BR/EDR Not Supported.
  • 05 03 0D 18 0F 18 — length 5, complete 16-bit UUID list (0x03), two UUIDs little-endian: 0x180D Heart Rate, 0x180F Battery.
  • 0A 09 50…30 — length 10, complete local name, UTF-8 Polar H10.
  • 02 0A 04 — length 2, TX power, signed +4 dBm.

Once you can walk that example, most other advertisements are the same loop with different type bytes. Manufacturer data and service data are where the interesting vendor formats hide.

Reference

AD types you will see constantly

The SIG defines dozens of AD types. In real traffic a handful dominate. Type codes below match what the decoder prints in each structure header.

TypeName
0x01
Flags

Discoverability and BR/EDR coexistence bits. Almost every legacy advertisement carries this as the first structure. Bit 1 (LE General Discoverable) and bit 2 (BR/EDR Not Supported) are the ones you will see most often.

0x02 / 0x03
16-bit service UUID list

Incomplete or complete list of GATT services the device exposes. UUIDs are little-endian on air (0x180D becomes 0D 18). Phones use this list for filtering and icon selection before a connection exists.

0x08 / 0x09
Local name

Shortened or complete device name as UTF-8. Easy to recognize in a hex dump, but many production devices omit it to save the 31-byte budget.

0x0A
TX power level

Signed dBm at the antenna. Combined with RSSI it is the usual rough distance estimate; alone it just tells you how hard the radio is transmitting.

0x16
Service data (16-bit UUID)

Service UUID plus a vendor- or profile-defined payload. Eddystone (0xFEAA), BTHome (0xFCD2), and Exposure Notification (0xFD6F) all live here.

0x19
Appearance

A 16-bit category/subcategory from the Bluetooth SIG appearance table (phone, watch, heart-rate sensor, etc.). OS UIs map this to an icon.

0xFF
Manufacturer specific data

Two-byte company identifier (little-endian) followed by vendor bytes. iBeacon, AltBeacon, Apple Continuity, Microsoft CDP, and most custom firmware formats use this type.

UUID lists also exist in 32-bit and 128-bit forms (0x040x07). A 128-bit UUID that sits on the Bluetooth Base UUID (0000xxxx-0000-1000-8000-00805F9B34FB) is just a 16-bit alias expanded for transport — the decoder shows both forms when it can.

Manufacturer data

Company IDs are not MAC vendors

Type 0xFF always starts with a 16-bit company identifier, little-endian, assigned by the Bluetooth SIG to member companies. Apple is 0x004C (4C 00 on air). Microsoft is 0x0006. Nordic Semiconductor is 0x0059. The remaining bytes are defined by that company — there is no universal layout after the ID.

This is a different namespace from the IEEE OUI in a public BD_ADDR. Confusing the two is a common debugging dead end: a device can advertise Apple manufacturer data while using a resolvable private address with no OUI at all. When you need to know who defined this payload, trust the company ID inside 0xFF. When you need the radio address, look at the address the stack reported for the advertisement — and be ready for it to be random.

The decoder resolves company IDs against the full Bluetooth SIG member list (thousands of entries). If the vendor publishes a public layout — iBeacon, AltBeacon, Microsoft CDP — the payload is decoded further. Otherwise you still get the company name and the raw trailing bytes to inspect.

Beacons

Common beacon and vendor formats

iBeacon (Apple company ID)

iBeacon is manufacturer data under Apple (0x004C) with type 0x02 and length 0x15 (21 data bytes after that header). Layout after the company ID:

  • 02 15 — iBeacon type and length
  • 16-byte proximity UUID (big-endian)
  • 2-byte major, 2-byte minor (big-endian)
  • 1-byte measured power (signed dBm at 1 m)
iBeacon example (Apple AirLocate UUID)
02 01 06  1A FF 4C 00 02 15 E2 C5 6D B5 DF FB 48 D2 B0 60 D0 F5 A7 10 96 E0 00 01 00 02 C5

Flags + 26-byte manufacturer structure. Major 1, minor 2, measured power −59 dBm. Use the iBeacon example button to open this payload in the tool.

Eddystone (service data 0xFEAA)

Eddystone does not use manufacturer data. It advertises service UUID 0xFEAA and puts the frame in service data (0x16). Frame type is the first data byte:

  • 0x00 UID — 10-byte namespace + 6-byte instance
  • 0x10 URL — TX power + compressed URL encoding
  • 0x20 TLM — battery mV, temperature, ADV count, uptime
  • 0x30 EID — ephemeral identifier (encrypted)

URL compression replaces common schemes and suffixes (https://www., .com/, …) with single-byte codes so a usable link fits the 31-byte budget. The decoder expands those codes back to a normal URL string.

AltBeacon, Apple Continuity, Microsoft CDP

AltBeacon is manufacturer data whose payload starts with big-endian 0xBEAC, followed by a 20-byte beacon ID, reference RSSI, and a manufacturer reserved byte. Any SIG company ID can carry it.

Apple Continuity reuses company 0x004C with many message types beyond iBeacon — Nearby Info, Handoff, AirPods pairing, Find My, and others. Types that are not publicly documented are labeled by name where known; the payload bytes stay raw.

Microsoft CDP beacons use company 0x0006 with scenario byte 0x01 (advertising beacon). The next byte encodes a device type Windows uses for Swift Pair / Nearby Share style discovery (desktop, phone, Xbox, …).

Sensors

BTHome and other service-data formats

Home Assistant and a growing set of sensors use BTHome on service UUID 0xFCD2. After the UUID, byte 0 is device info: version in the high bits, encryption flag in bit 0. Unencrypted v2 objects then follow as [object_id][value…] — for example object 0x02 is temperature as a signed 16-bit little-endian value in 0.01 °C units.

Google/Apple Exposure Notification used service UUID 0xFD6F with a 16-byte rolling proximity identifier and encrypted metadata. You will still see these frames in historical captures; the decoder labels them without trying to decrypt.

If service data uses a UUID the tool does not recognize, you still get the UUID (resolved against SIG assigned numbers when possible) and a hex dump of the payload — enough to start reading a proprietary format against the vendor's docs.

Practice

Where the bytes come from

Useful places to grab a payload to paste here:

  • nRF Connect (mobile or desktop) — open the device, copy the raw advertising hex.
  • Wireshark with a BLE sniffer — look at the Advertising Data field of ADV_IND / ADV_NONCONN_IND / scan response packets.
  • Android btsnoop HCI log — enable HCI snoop in developer options, pull the log, open in Wireshark, same advertising fields.
  • Your own scanner — CoreBluetooth (advertisementData), Android ScanRecord.getBytes(), Windows BluetoothLEAdvertisement, or SimpleBLE's scan results. Some stacks hand you already-split fields; others give the raw AD buffer this tool expects.

Input may use spaces, colons, commas, 0x prefixes, C array braces, or Base64 encoding.

Platform notes

Why the same device looks different on every OS

The bytes on air are identical. What your application receives is not, because each OS Bluetooth stack filters, merges, and renames fields differently.

  • macOS / iOS — CoreBluetooth never exposes the device address to apps. You get a per-device UUID that is stable on that Mac or phone, not the BD_ADDR. Manufacturer data and service data show up as dictionary keys, not always as a single contiguous AD buffer.
  • Windows — the stack may merge advertising and scan-response data before your app sees them, on its own schedule. A field that appears only in the scan response can show up “late” relative to the first advertisement callback.
  • Android — scan results are heavily filtered and batched. Hardware and OS versions differ on whether you see every ADV or a throttled subset. ScanRecord.getBytes() is the closest thing to the on-air AD buffer; parsed helpers can drop types they do not model.
  • Linux — BlueZ via D-Bus or HCI gives you relatively raw access, which is why embedded and gateway tooling often feels “more honest” than mobile stacks — and why captures from Linux are good ground truth when mobile APIs disagree.

If two platforms disagree about a device's name, services, or manufacturer blob, decode a raw capture first. The on-air payload is the contract; the OS API is an interpretation. Cross-platform libraries (including SimpleBLE) exist largely to absorb those interpretation differences so product code does not.

Addresses

Private addresses and failed MAC lookups

Modern BLE peripherals usually advertise with a resolvable private address that rotates on a timer. Those addresses intentionally do not embed an IEEE OUI, so MAC vendor lookup databases return nothing — or worse, a misleading hit on a recycled public prefix.

A public address still carries an OUI in the top three bytes and can be looked up. The advertisement itself may also include an LE device address AD type (0x1B) with an explicit public/random bit. Prefer that bit, and prefer the company ID inside manufacturer data, over guessing from a random address string.

Legacy limits

31 bytes, advertising PDUs, and scan responses

On Bluetooth 4.x-style advertising, connectable and non-connectable PDUs give you 31 bytes of Host advertising data. Devices often split content across two events:

  • the advertising packet (always broadcast on the primary channels), and
  • the scan response (sent only when an active scanner requests it) — commonly used for the full local name or extra service UUIDs.

Many host APIs merge those two buffers before your app sees them, so a paste longer than 31 bytes is often “ADV + scan response,” not a single on-air PDU. The tool warns above 31 bytes for that reason. The parser still walks length/type/data to the end of whatever you paste.

Zero-length structures are treated as padding (common when firmware zero-fills a fixed buffer). Trailing padding is reported, not decoded as fields.

BLE 5

Extended advertising: larger payloads on secondary channels

Extended advertising (Bluetooth 5.0+) keeps the same AD structure encoding but changes where and how large that data can be. Legacy advertising puts the entire payload on primary channels 37, 38, and 39. Extended advertising usually puts only a short header on the primary channels and moves the real data onto secondary (data) channels.

Primary pointer, secondary payload

A typical extended flow looks like this on air:

  1. ADV_EXT_IND on a primary channel — small PDU. Often carries little or no advertising data; instead it includes an AuxPtr (channel, offset, PHY) telling the scanner where to listen next.
  2. AUX_ADV_IND on a secondary channel — carries the bulk of the advertising data (and may itself point to further packets).
  3. Optional AUX_CHAIN_IND packets if the Host data does not fit in one secondary PDU.
  4. Optional AUX_SCAN_RSP when an active scanner requests more data from a scannable extended advertisement.

So “the advertisement” your application receives is often a reconstruction: the controller followed AuxPtr, assembled chained fragments, and handed the Host a single advertising-data buffer. A raw HCI sniffer log still shows the individual extended PDUs; a mobile scan callback usually does not.

How much data fits

A single secondary advertising PDU can carry on the order of ~254 bytes of advertising data (exact usable size depends on which header fields are present — address, ADI, CTE, and so on consume room). With chaining, the Host can expose up to 1650 bytes of advertising data per advertising set — enough for long manufacturer blobs, full URLs without Eddystone-style compression, or several service-data blocks that never fit in 31 bytes.

The bytes inside that buffer are still ordinary AD structures. This decoder does not parse AuxPtr or radio headers; paste the advertising data (or ADV + scan response merge) your stack gave you, and the same length/type/data walk applies whether the source was legacy or extended.

ADI, sets, and PHYs

  • Advertising Data Info (ADI) — a small field (SID + DID) that lets scanners match fragments of the same logical advertisement and detect when data has changed between events.
  • Advertising sets — a controller can run multiple concurrent advertisements (different data, intervals, or connectable modes), each identified by a handle / set ID. Useful when one product must look like a beacon and a connectable peripheral at once.
  • PHY choices — primary extended PDUs are typically on LE 1M; secondary can use LE 1M, LE 2M, or LE Coded. Coded PHY trades throughput for range; scanners that only listen on 1M will miss secondary data sent only on Coded.
  • Anonymous advertising — extended PDUs can omit the device address from the packet. Useful for privacy; painful if your debug workflow assumed every ADV carries a BD_ADDR.

Periodic advertising (related, not the same)

Periodic advertising builds on the extended machinery: an extended advertisement can point at a periodic train (AUX_SYNC_IND / periodic PDUs) that repeats on a fixed interval without the scanner re-discovering AuxPtr every time. Once synchronized, the central receives a steady stream of data (sensor updates, broadcast isochronous building blocks, large status blobs) more efficiently than reconnecting. Periodic payloads still use AD structures when the Host puts advertising data in them — again, the decoder cares about those structures, not the sync handshake.

What breaks in practice

  • Scanner support is uneven. The peripheral may send a perfect extended advertisement; a phone OS or USB dongle that does not follow secondary channels will show empty or incomplete data. Always confirm the scanner path before blaming firmware.
  • Legacy-only filters still exist. Some APIs and UI tools were written for 31-byte records. They may truncate, ignore extended-only devices, or only surface fields they already knew how to map.
  • Captures look different by layer. Wireshark on a sniffer shows ADV_EXT_IND → AUX_ADV_IND chains. An app-level log may show one combined buffer. When comparing two hex dumps, check whether you are looking at Host AdvData or raw link-layer PDUs.
  • Connectable + large data is a product choice. Extended advertising can be connectable, scannable, or non-connectable independently of payload size. Beacon-style devices often stay non-connectable with large manufacturer data; peripherals that need a GATT connection still advertise connectable sets — and may use a second set for the bulky broadcast.

If you paste extended advertising data here and the structures look right but your phone app does not see the device the same way, the mismatch is usually in scanning support or Host merging — not in the AD encoding itself.