Skip to content

Connect a device with a certificate

IoT Pulse devices connect to the MQTT broker with a client certificate instead of a username and password. There are two ways to get one:

  1. Self-enrollment — the device generates its own private key and a certificate signing request (CSR), then redeems a one-time claim code for a signed certificate. The private key never leaves the device.
  2. Platform-generated bundle — an admin issues a certificate (and its private key) from the admin app and installs it on the device by hand.

This page covers both, connecting to the broker, renewing and rotating certificates, and the most common causes of a connection failure.

Can the device generate a keypair and a CSR itself? Use
Yes — it has an MQTT stack you control (custom firmware, a script-driven gateway, a generic MQTT client you can script) Self-enrollment
No — it only accepts a certificate you hand it (a web-configured gateway UI, mosquitto, a generic MQTT library with no CSR tooling) Platform-generated bundle

Self-enrollment is the better default whenever it’s available: the device’s private key is generated on-device and is never seen by the platform. Reach for a platform-generated bundle only when the device genuinely can’t produce its own CSR.

An admin mints a one-time claim code in the admin app under Device Management → Gateway Enrollment, or with a scoped provisioning:write API key:

Terminal window
curl -X POST https://api.iotpulse.io/api/v1/provisioning/claims \
-H "X-API-Key: iotp_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"intendedName": "Dock 3 gateway",
"expiresInMinutes": 60,
"intendedDeviceTypeId": "your-device-type-id"
}'

The response’s claimCode (format PULSE-XXXX-XXXX-XXXX) is returned exactly once — store it somewhere the device can read it during first boot. A claim expires (default cap 24 hours) and can be used only a limited number of times (maxUses, default 1).

Generate an EC P-256 private key and a certificate signing request. The subject fields you put in the CSR are ignored — the platform assigns the certificate’s subject itself — so any placeholder value is fine:

Terminal window
openssl ecparam -name prime256v1 -genkey -noout -out device.key
openssl req -new -key device.key -out device.csr -subj "/CN=device"

Keep device.key on the device. It is never sent anywhere.

Terminal window
curl -X POST https://api.iotpulse.io/api/provisioning/claim \
-H "Content-Type: application/json" \
-d '{
"claimCode": "PULSE-XXXX-XXXX-XXXX",
"hardwareFingerprint": "unique-serial-or-mac",
"hardwareModel": "your-device-model",
"agentVersion": "1.0.0",
"csr": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----"
}'

This endpoint takes no API key — the claim code is the credential — and each field means:

Request field Required Meaning
claimCode Yes The one-time code from step 1.
hardwareFingerprint Yes A unique identifier for this physical device (serial number, MAC address).
hardwareModel Yes The device’s model designation.
agentVersion Yes The firmware/agent version making the request.
csr Yes The PEM-encoded CSR from step 2.
agentCapabilities No A list of capability strings, if your integration reports any.
reportedMacAddress No The device’s MAC address, if distinct from hardwareFingerprint.

A successful redemption returns:

Response field Meaning
deviceId The device’s id in your tenant.
mqtt.brokerUrl The broker to connect to (see Connecting to the broker).
mqtt.clientCert Your signed leaf certificate (PEM).
mqtt.clientCertChain The issuing intermediate certificate(s) (PEM) — required alongside the leaf.
mqtt.serverCaBundle The CA bundle that verifies the broker’s TLS certificate — a different trust chain than your own leaf/chain (see below).
mqtt.topicPrefix The tenant-scoped prefix your published topics fall under.
certExpiresAt When this certificate expires.
renewAfterUtc When to start attempting renewal.
renewalEndpoint /api/provisioning/cert/renew (see next section).

Store clientCert, clientCertChain, and serverCaBundle on the device (for example as client-cert.pem, client-cert-chain.pem, and server-ca-bundle.pem) alongside the private key from step 2. Also persist deviceId — you’ll need it as your MQTT client ID on every connection — and renewAfterUtc, so the device knows when to renew without calling this endpoint again.

A self-enrolled certificate is valid for 30 days. Starting at renewAfterUtc (day 21), renew it over mTLS — your current, still-valid certificate is itself the authentication:

Terminal window
curl -X POST https://api.iotpulse.io/api/provisioning/cert/renew \
--cert client-cert.pem --key device.key \
-H "Content-Type: application/json" \
-d '{ "csr": "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----" }'

Don’t pass --cacert: api.iotpulse.io uses a publicly-trusted certificate, so your device’s own trust store (or curl’s default one) already verifies it — pointing --cacert at your device certificate chain would instead make curl try to verify api.iotpulse.io against your own device PKI, which fails.

You can submit a fresh CSR (a new keypair) or resubmit a CSR for the same key — either is accepted. The response carries new clientCert, clientCertChain, serverCaBundle, certExpiresAt, and renewAfterUtc fields.

An admin issues the certificate (and its private key) from the admin app, under Device Management → Device Certificates, and installs it on the device by hand.

  1. Select the target device and choose Issue certificate.
  2. Download the certificate once — it is not shown or retrievable again. Choose the format the device needs:
    • PEM — separate private key, leaf certificate, and issuing chain files, for a device or client library that takes them individually.
    • PKCS#12 (.p12) — the key, leaf, and chain bundled into one password-protected file, for a device UI (for example a gateway’s web configuration page) that only accepts a single import file. The passphrase is shown once, alongside the download, and is not stored.
  3. Install the downloaded file(s) on the device following its own documentation for importing a client certificate.

To script this, use iotpulse certs issue <deviceId> --out <dir> or POST /api/v1/device-certificates (API reference), with an API key that has been granted device-certificates:issue explicitly.

A platform-generated bundle is valid for 1 year. The admin app’s certificate list flags a bundle as expiring starting 30 days before it does — there is no automated renewal for this mode, so watch for that warning and rotate before it expires.

A device may hold at most 2 live platform-generated certificates at once, which fixes the rotation order:

  1. Issue a new certificate for the device.
  2. Install it on the device (replacing the old one).
  3. Revoke the old certificate, once the device is confirmed on the new one.

Issuing never revokes an existing certificate on its own — the old one keeps working until you revoke it, which is what makes it safe to overlap the two during rotation.

Whichever mode you used, connecting looks the same:

Setting Value
Host messaging.iotpulse.io
Port 8883 (MQTT over TLS)
Client certificate Your leaf certificate (clientCert — the file you stored as client-cert.pem).
Client certificate chain The issuing intermediate(s) (clientCertChain — client-cert-chain.pem) — most MQTT clients need this presented alongside the leaf, not installed separately.
Client private key Your private key — the one you generated (mode 1) or downloaded once (mode 2).
Server CA / trust anchor serverCaBundle (server-ca-bundle.pem) — verifies the broker’s own TLS certificate. This is a different chain than your client certificate’s issuer — don’t substitute one for the other.
Client ID Must equal your device’s id exactly (case-sensitive). A mismatched client ID is rejected at connect.

A certificate device’s MQTT client ID is its device id, exactly. The broker refuses the certificate under any other client ID, including a re-cased, padded, or account-prefixed one.

Client IDs are shared across the whole broker, not kept per account, and a second connection under a client ID that is already connected takes over the first one’s session. So a device id can back live certificates in only one account at a time. Issuing a certificate, whether by redeeming a claim or as a platform-generated bundle, is refused with 409 client_id_in_use while a certificate device in another account still holds a valid (unrevoked, unexpired) certificate for the same device id. Nothing is issued, and the claim use is not consumed. A device id can’t be changed, so for a platform-generated bundle, add a new device (it gets a new id) and issue its bundle instead. A first-time claim redemption assigns a fresh random device id each time, so redeeming the claim again is enough. Within your own account, a device may hold more than one certificate: a re-enrollment, or a platform-generated bundle alongside a self-enrolled certificate, keeps the same client ID. Renewal is never refused this way.

Issuance is also refused with 409 client_id_in_use when a client that signs in with a username and password already connects under that device id: an integration gateway, or one of the platform’s own services. A certificate under that ID would take over the other client’s session.

Your certificate authorizes publishing only on your tenant’s topic prefix (topicPrefix from the redemption/issuance response) — specifically, the exact topic(s) your device type is configured to publish on, never a tenant-wide wildcard.

  • An expired certificate is refused at the next connection attempt. It does not disconnect an already-open session on its own.
  • A revoked certificate is refused at the next connection attempt, and any session it already opened is disconnected immediately — except the certificate a successful mTLS renewal (mode 1) just replaced, which is revoked without disconnecting its current session; it keeps working until the device’s next reconnect, which must be on the new certificate.
  • Revoking a certificate does not affect any other certificate the device holds (relevant during a mode-2 rotation overlap, or if a device was re-enrolled).
Symptom Likely cause
TLS handshake fails immediately Missing or wrong clientCertChain — most clients need the leaf and the issuing chain presented together, not the leaf alone.
TLS handshake fails verifying the broker Wrong or missing serverCaBundle — this is a separate trust chain from your client certificate’s issuer; don’t reuse one for the other.
Broker accepts TLS but immediately disconnects Client ID doesn’t exactly match your device id (case-sensitive), or your certificate has been revoked.
410 claim_expired / claim_revoked redeeming a claim The claim’s lifetime ran out, or it was revoked — mint a new one.
409 claim_exhausted redeeming a claim The claim already hit its maxUses limit — mint a new one.
409 client_id_in_use redeeming a claim or issuing a bundle The device id is its MQTT client ID, and a certificate device in another account, or a username/password client, already uses it. Issue the bundle for a new device (a device id can’t be changed), or redeem the claim again (a first-time redemption picks a new random id) — see The client ID rule.
401 reenrollment_proof_required redeeming a claim This device already enrolled once; re-enrolling needs a valid certificate presented over mTLS, or an admin can delete the device to allow a first-time enrollment again.
Certificate signed but device can’t publish Your device’s configured type doesn’t resolve a publish topic yet. Confirm the device’s type is set (or intendedDeviceTypeId was on the claim) — this only takes effect once the device reconnects on a renewed or reissued certificate, not on the certificate already issued or on the current session.
Renewal call fails, then a retry also fails with 401 cert_invalid The first attempt actually succeeded and revoked the certificate you’re retrying with — see the caution notes in Renew before expiry. An admin must delete the device or issue a platform-generated bundle to recover it; a self-service claim redemption won’t work.
Connection works but stops after a while, no revoke performed Certificate expired — check certExpiresAt and renew (mode 1) or reissue (mode 2).
  • Public API reference — minting and managing enrollment claims with a scoped API key.
  • Devices — managing devices in the admin app.