Documentation API

SDK Node.js

Le client Node.js officiel de HonkIO : installation, démarrage rapide et chaque ressource, avec des exemples.

Installation et démarrage rapide

Installez le paquet, puis créez un client avec une clé API.

bash
npm install @honkio/node

Créez une clé dans le tableau de bord, sous API Keys → Create key; la clé complète n'est affichée qu'une fois, juste après sa création. Commencez avec une clé de test (mk_test_...) : rien n'est livré ni facturé.

javascript
import { Honkio } from '@honkio/node'

const honkio = new Honkio(process.env.HONKIO_API_KEY)

const { data, error } = await honkio.messages.send({
  from: '+1416XXXXXXX', // one of your HonkIO numbers
  to: '+1613XXXXXXX',   // a Canadian number you hold consent for
  body: 'Hello from HonkIO!',
})

if (error) {
  console.error(error.name, error.message) // e.g. NON_CANADIAN_NUMBER
} else {
  console.log(data.id, data.status)
}

new Honkio() sans argument lit HONKIO_API_KEY, et lève une exception si ni l'un ni l'autre n'est défini.

Résultats et erreurs

data contient la réponse de l'API telle qu'envoyée, en snake_case (scheduled_at, segment_count). En cas d'échec, error est { name, message, statusCode, details? }.

nameQuand
un code de l'API tel que NON_CANADIAN_NUMBERl'API a refusé la requête; statusCode est son statut HTTP
network_erroraucune réponse n'est arrivée (DNS, connexion refusée, délai de 30 secondes); statusCode est null
application_errorle corps de la réponse n'était pas du JSON, par exemple une page d'erreur d'un proxy
invalid_argumentun identifiant était vide, . ou .., ou un appel de consentement nommait les deux sujets ou aucun; rien n'a été envoyé
invalid_signaturewebhooks.verify (ou verifyWebhook) n'a pas pu vérifier une livraison

details contient le details de l'API quand elle en a envoyé un (chemins de validation, retry_after, clés de modèle manquantes). Sinon, il contient tout ce que l'API a envoyé à côté de l'enveloppe d'erreur, comme attempts_remaining sur VERIFICATION_INVALID_CODE.

Les codes généraux sont listés sur la page Plateforme, les codes SMS sur la page SMS, et les codes de courriel sur la page Courriel.

Écrivez les champs de requête en camelCase (replyTo, scheduledAt, isCommercial, dnclExemptions, skipConsentCheck); ils sont envoyés en snake_case, comme l'attend l'API. Les clés à l'intérieur de variables, headers et metadata sont les vôtres et sont envoyées telles quelles.

Options et clés

Le second argument de new Honkio prend deux options : baseUrl, qui vaut https://api.honkio.ca par défaut, et fetch, qui vaut le fetch global par défaut (Node 18 ou plus récent). Passez votre propre fetch pour utiliser un polyfill ou pour intercepter les requêtes.

javascript
const honkio = new Honkio('mk_live_...', {
  baseUrl: 'https://api.honkio.ca', // default
  fetch: myFetch,                   // default: the global fetch
})

Le préfixe d'une clé est son mode : les clés mk_test_ simulent tout et n'atteignent jamais l'opérateur ni votre solde; les clés mk_live_ envoient pour vrai. Rien d'autre ne change dans l'appel au SDK entre les deux.

Ressources

Chaque ressource du client, et les méthodes qu’elle expose :

RessourceMéthodes
messages (SMS)send({ from, to, body }, { idempotencyKey }), get(id), list(query)
phoneNumbersareaCodes(), search({ areaCodes, limit }), provision({ phoneNumber }), list(), get(id), release(id)
verifystart({ to, from, appName, codeLength, ttlMinutes }), check(id, { code }), get(id), list({ status, limit, offset })
consentscreate, list, check({ phoneNumber } or { emailAddress }), revoke(...)
webhookscreate, list, get, update, remove, verify(rawBody, headers, secret), deliveries(id), deadLetters(id), replay(deadLetterId), discard(deadLetterId), reactivate(id), rotateSecret(id)
emailssend(body, { idempotencyKey }), get(id), list(query), update(id, { scheduledAt }), cancel(id)
emails.receivedlist(query), address(), setAddressEnabled(enabled), get(id, { htmlFormat }), raw(id), attachment(id, attachmentId), simulate(body)
batchsend([...up to 100 emails]) or send({ template, recipients: [...up to 500] })
domainscreate({ domain }), get(id), list(), update(id, { openTracking, clickTracking, receiving }), verify(id), remove(id)
suppressionscreate({ emailAddress }), list({ reason }), remove(emailAddress)
templatescreate, get, list, update, publish, rollback(idOrAlias, { version }), versions, remove

D'autres ressources

De courts exemples pour le reste :

javascript
// Record consent
await honkio.consents.create({
  phoneNumber: '+1613XXXXXXX',
  consentType: 'express',
  sourceDescription: 'Website opt-in form',
})

// Add a sending domain
await honkio.domains.create({ domain: 'mail.acme.ca' })

// List bounces and complaints
await honkio.suppressions.list({ reason: 'hard_bounce' })

// Publish a template draft
await honkio.templates.publish('shipping-update')

// One template, up to 500 recipients
await honkio.batch.send({
  from: 'noreply@mail.acme.ca',
  template: { id: 'shipping-update' },
  recipients: [{ to: 'ada@example.com', variables: { firstName: 'Ada' } }],
})

Numéros de téléphone

search prend des indicatifs régionaux canadiens actifs (areaCodes() les liste par province) ou un préfixe sans frais; limité à 30 recherches par minute. provision exige une clé réelle et facture le premier mois de location plus des frais d'activation uniques, tous deux indiqués sur chaque résultat de recherche et sur GET /v1/pricing.

javascript
const { data: available } = await honkio.phoneNumbers.search({ areaCodes: ['416', '647'], limit: 5 })
// available[0]: { phone_number, region, upfront_cost_cents, activation_fee_cents, monthly_cost_cents, ... }

const { data: number, error } = await honkio.phoneNumbers.provision({
  phoneNumber: available![0]!.phone_number,
})

L'API ne lit pas d'Idempotency-Key sur cette route, donc provision n'en prend pas : les achats d'un même compte se font un à la fois (PURCHASE_IN_PROGRESS, réessayez sous peu), et un numéro que vous possédez déjà répond 409 CONFLICT, donc une nouvelle tentative après un délai ne peut pas l'acheter deux fois. Après un network_error, appelez list() avant de réessayer.

Vérifier un numéro de téléphone

Une vérification coûte le tarif par segment plus une surcharge de vérification; celle que l'opérateur refuse est remboursée. Les codes ont 6 chiffres par défaut (codeLength : 4, 6 ou 8) et sont valides 10 minutes (ttlMinutes, de 1 à 60). Cinq codes erronés terminent la vérification avec VERIFICATION_MAX_ATTEMPTS, et une vérification expirée répond VERIFICATION_EXPIRED. Avec une clé de test, rien n'est envoyé et le code est composé de zéros.

javascript
const { data: verification } = await honkio.verify.start({
  from: '+1416XXXXXXX', // one of your HonkIO numbers
  to: '+1613XXXXXXX',
  appName: 'Acme',
})

const { data, error } = await honkio.verify.check(verification!.id, { code: '123456' })
if (error?.name === 'VERIFICATION_INVALID_CODE') {
  console.log(error.details) // { attempts_remaining: 4 }
} else if (data) {
  console.log(data.status) // 'verified'
}

start ne prend pas de clé d'idempotence : un seul démarrage par destinataire toutes les 60 secondes, une nouvelle tentative dans cette fenêtre répond RATE_LIMITED. Après un network_error, listez les vérifications en attente et faites correspondre le numéro avant d'en démarrer une nouvelle.

Courriel

javascript
const { data, error } = await honkio.emails.send({
  from: 'Acme <onboarding@test.honkio.ca>',
  to: 'delivered@test.honkio.ca',
  subject: 'Hello from HonkIO',
  html: '<p>It works.</p>',
})

isCommercial vaut false par défaut : le courriel est transactionnel à moins de le définir. Le courriel commercial exige isCommercial: true, un consentement LCAP enregistré pour le destinataire, un seul destinataire, et un pied de page de désabonnement.

Réception de courriel

Chaque compte reçoit gratuitement une adresse de réception gérée (address(), enabled dans la réponse). Un domaine vérifié pour l'envoi peut aussi recevoir son propre courriel une fois activé. list et get renvoient le message analysé : en-têtes, text ou html, verdicts, et métadonnées des pièces jointes.

javascript
const { data: address } = await honkio.emails.received.address()
console.log(address.example) // anything@<your-slug>.inbound.honkio.ca

const { data: page } = await honkio.emails.received.list({ limit: 10 })
const { data: email } = await honkio.emails.received.get(page!.data[0]!.id)
console.log(email.from, email.subject, email.verdicts)

raw et attachment téléchargent des octets, pas du JSON : les deux résolvent data en un Blob, à lire avec arrayBuffer() ou à rediriger vers un fichier. Les deux répondent ATTACHMENT_EXPIRED une fois la fenêtre de rétention de 40 jours passée.

simulate fabrique un courriel reçu avec une clé de test, utile pour tester votre intégration sans expéditeur réel.

Webhooks

Passez le corps brut, pas du JSON déjà analysé, à webhooks.verify (ou l'export autonome verifyWebhook). Il ne lève jamais d'exception : un corps déjà analysé, un secret manquant ou des en-têtes manquants répondent invalid_argument, et une signature invalide ou périmée répond invalid_signature.

javascript
const webhookSecret = process.env.HONKIO_WEBHOOK_SECRET
if (!webhookSecret) throw new Error('Set HONKIO_WEBHOOK_SECRET')

app.post('/webhooks/honkio', express.raw({ type: 'application/json' }), (req, res) => {
  const { data: event, error } = honkio.webhooks.verify(req.body, req.headers, webhookSecret)
  if (error) return res.status(400).end()
  if (!event.livemode) console.log('test event')
  res.status(200).end()
})

La signature est du HMAC-SHA256, en hexadécimal, sur l'horodatage et le corps brut, envoyée dans X-HonkIO-Signature avec X-HonkIO-Timestamp et X-HonkIO-Event. Les livraisons de plus de 300 secondes sont refusées par défaut; changez cela avec toleranceSeconds.

Un événement vérifié est l'enveloppe : id, type, created, account_id, livemode, data. Dédupliquez sur id : une livraison échouée est retentée pendant environ 24 heures, chaque tentative signée à nouveau avec le même id d'événement.

deliveries(id) liste les tentatives récentes. Les événements qui ont échoué à chaque tentative sont des lettres mortes : deadLetters(id) les liste, replay(deadLetterId) en renvoie une, et discard(deadLetterId) l'abandonne. reactivate(id) réactive un point de terminaison que la plateforme a désactivé.

Pagination

Les appels de liste suivent l'une de trois formes. Par page (page, limit, et un objet meta avec page, limit, total et pages) : messages.list et consents.list. Par décalage (limit, offset) : verify.list. Par curseur (limit, cursor, répondant avec data, has_more et next_cursor) : emails.list, emails.received.list, suppressions.list et templates.list. Tout le reste, comme phoneNumbers.list et webhooks.list, renvoie chaque ligne en un seul appel.