Introduction

Introduction

KD Push est un service push entièrement géré pour les plateformes et leurs marchands. Les marchands ne touchent jamais à OneSignal, à Firebase ni aux certificats Apple : ils ajoutent leur application dans le portail, récupèrent une App Key et une Server Key, et leur plateforme envoie via cette API.

Your platform
→
KD Push API
→
Queue & engine
→
APNs / FCM
→
Customers' devices

Toutes les requêtes et réponses sont en JSON sur HTTPS, les dates en ISO 8601 et les identifiants publics sont des ULID.

Quick start

Démarrage rapide

  1. 1
    Créez votre application et récupérez ses clés
    Dans le portail marchand : Nouvelle application → copiez l’App Key et la Server Key (affichée une seule fois).
  2. 2
    Enregistrez l’appareil depuis l’application
    cURL
    curl -X POST https://push.kd-app.com/api/v1/sdk/devices \
      -H "X-KD-App-Key: kdp_app_…" \
      -H "Content-Type: application/json" \
      -d '{
        "platform": "android",
        "token": "fcm-registration-token",
        "installation_id": "3f0c9a2e-6b1d-4c8e-9f7a-2d5e8b1c4a90",
        "android": { "fcm_project_id": "neon-push" },
        "external_user_id": "42",
        "user_signature": "9f2c4e…",
        "locale": "ar",
        "app_version": "2.1.0"
      }'
  3. 3
    Envoyez depuis votre serveur
    cURL
    curl -X POST https://push.kd-app.com/api/v1/messages \
      -H "Authorization: Bearer kdp_live_…" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: app-created-1234" \
      -d '{
        "title": "تطبيق جديد",
        "body": "تمت إضافة واتساب الذهبي للمتجر",
        "url": "neonstore://app/1234",
        "image_url": "https://cdn.example.com/icons/1234.png",
        "data": { "app_id": "1234" },
        "audience": { "type": "all" }
      }'
Authentication

Authentification

Chaque application a deux types de clés, chacune à une seule place :

App Key
kdp_app_…
X-KD-App-Key: kdp_app_…
Où :
Dans l’application mobile (publique)
Peut :
Enregistrer les appareils, lire la configuration, signaler les ouvertures
Ne peut jamais envoyer
Server Key
kdp_live_…
Authorization: Bearer kdp_live_…
Où :
Uniquement sur le serveur de votre plateforme (secrète)
Peut :
Envoyer, suivre, annuler, statistiques
Affichée une seule fois — nous ne stockons qu’un hash
La clé décide de l’application. Aucun endpoint n’accepte d’id d’application ou de marchand dans le corps : la clé d’une application ne peut jamais atteindre les appareils ou messages d’une autre — elle reçoit simplement 404.
Errors

Erreurs

Toutes les erreurs ont la même forme et ne révèlent jamais de détails internes :

422 Unprocessable Entity
{
  "error": {
    "code": "validation_failed",
    "message": "The body field is required.",
    "fields": {
      "body": [
        "The body field is required."
      ]
    }
  }
}
401invalid_app_key
App Key inconnue ou application désactivée.
401invalid_server_key
Server Key inconnue ou révoquée.
402subscription_inactive
Le marchand n’a pas d’abonnement actif.
402quota_exceeded
Le quota mensuel de notifications de l’offre est épuisé.
403invalid_user_signature
La signature ne correspond pas à external_user_id.
403web_disabled · origin_not_allowed
Le web est désactivé pour l’application, ou la page n’est pas sur l’un de ses sites.
422invalid_subscription · provider_mismatch
Clés d’abonnement du navigateur inutilisables, ou plateforme et fournisseur incompatibles.
403app_disabled · account_banned
Application désactivée ou compte banni.
404not_found
Introuvable — ou appartient à une autre application.
409not_cancelable
La notification est déjà dans un état final.
422validation_failed
Données invalides ; détails dans fields.
422invalid_token
Jeton d’appareil mal formé.
429rate_limited
Trop de requêtes ; attendez selon Retry-After.
500server_error
Erreur interne — réessayez plus tard.
Rate limits

Limites de débit

60
req. / min
Endpoints de l’application, par IP
20,000
req. / min
Endpoints de l’application, par application
600
req. / min
Endpoints serveur, par Server Key

Au-delà de la limite, vous recevez 429 rate_limited avec un en-tête Retry-After. Envoyer à de nombreux appareils reste une seule requête — le moteur la répartit en interne.

Idempotency

Idempotence

Envoyez un en-tête Idempotency-Key avec chaque envoi (p. ex. l’id de votre événement). Répéter la requête avec la même clé dans les 24 h renvoie la même notification avec 200 au lieu d’en créer une nouvelle — réessayer après une coupure réseau est donc toujours sûr.

Header
Idempotency-Key: app-created-1234-v2.1.0
Mobile app

Configuration

GET/sdk/configApp Key — application mobile

Récupérez-la au démarrage de l’application. Android y lit ses options Firebase : changer de projet Firebase ou de fournisseur n’exige donc jamais de recompiler l’application.

Peut être mise en cache — la réponse porte Cache-Control: max-age=300.
Request
curl https://push.kd-app.com/api/v1/sdk/config \
  -H "X-KD-App-Key: kdp_app_…"
200 OK
{
  "app": {
    "id": "01JB2Q6V8K4Y3N5M7R9T1W0XZC",
    "name": "My Store"
  },
  "identity_verification": "required",
  "android": {
    "provider": "fcm",
    "firebase": {
      "project_id": "neon-push",
      "app_id": "1:123456789:android:abcdef",
      "api_key": "AIza…",
      "sender_id": "123456789",
      "storage_bucket": null
    }
  },
  "ios": {
    "provider": "apns"
  },
  "web": {
    "provider": "webpush",
    "vapid_public_key": "BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U",
    "icon_url": null,
    "prompt": {
      "enabled": true,
      "delay_seconds": 6,
      "title": null,
      "text": null
    }
  }
}
Mobile app

Enregistrer un appareil

POST/sdk/devicesApp Key — application mobile

Appelez-le à chaque démarrage de l’application, renouvellement du jeton, connexion et déconnexion. 201 la première fois, 200 ensuite.

ChampDescription
platformios | android | webobligatoire
Plateforme de l’appareil.
tokenstringobligatoire
Jeton APNs (hex) ou jeton d’enregistrement FCM ; pour un navigateur, l’endpoint de l’abonnement.
installation_iduuidfacultatif
Un id stable que vous générez une fois et conservez dans l’application pour chaque App Key. Relie les renouvellements du jeton au même appareil.
providerapns | fcm | webpushfacultatif
Déduit de la plateforme s’il est omis.
ios.bundle_idstringfacultatif
La valeur de Bundle.main.bundleIdentifier.
ios.profile_app_idstringfacultatif
application-identifier issu de embedded.mobileprovision, sans le préfixe d’équipe.
ios.team_idstring(10)facultatif
TeamIdentifier du même fichier.
ios.environmentproduction | sandboxfacultatif
La valeur de aps-environment.
android.fcm_project_idstringfacultatif
Le projet Firebase qui a émis le jeton (depuis /sdk/config).
web.p256dh · web.authbase64urlselon le cas
Les clés de l’abonnement du navigateur (depuis PushSubscription.toJSON()). Obligatoires pour le web ; acceptées uniquement depuis une page de l’un des sites de l’application.
external_user_idstring | nullfacultatif
Votre id utilisateur. Envoyez null à la déconnexion.
user_signaturehexselon le cas
Obligatoire avec external_user_id tant que la vérification d’identité est active. Comment la calculer
tagsobjectfacultatif
Jusqu’à 20 tags pour le ciblage ; clés [A-Za-z0-9_.-], valeurs scalaires courtes.
locale · timezone · app_version · os_version · device_model · sdk_versionstringfacultatif
Affiché dans le portail marchand.
Un jeton appartient à une seule application. Si une autre application l’enregistre (p. ex. l’APK d’une autre boutique installé par-dessus le vôtre), l’appareil passe chez elle et son utilisateur et ses tags sont effacés — vos notifications n’atteignent jamais le client d’une autre boutique.
Request
curl -X POST https://push.kd-app.com/api/v1/sdk/devices \
  -H "X-KD-App-Key: kdp_app_…" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "android",
    "token": "fcm-registration-token",
    "installation_id": "3f0c9a2e-6b1d-4c8e-9f7a-2d5e8b1c4a90",
    "android": { "fcm_project_id": "neon-push" },
    "external_user_id": "42",
    "user_signature": "9f2c4e…",
    "locale": "ar",
    "app_version": "2.1.0"
  }'
201 Created
{
  "device": {
    "installation_id": "3f0c9a2e-6b1d-4c8e-9f7a-2d5e8b1c4a90",
    "platform": "android",
    "status": "active",
    "external_user_id": "42"
  }
}
403 Forbidden
{
  "error": {
    "code": "invalid_user_signature",
    "message": "user_signature does not match external_user_id."
  }
}
Mobile app

Désabonnement

POST/sdk/devices/unsubscribeApp Key — application mobile

Quand l’utilisateur désactive les notifications dans l’application. L’appareil ne reçoit plus rien jusqu’à ce qu’il s’enregistre à nouveau.

ChampDescription
installation_iduuidobligatoire
Le même id que lors de l’enregistrement.
Request
curl -X POST https://push.kd-app.com/api/v1/sdk/devices/unsubscribe \
  -H "X-KD-App-Key: kdp_app_…" \
  -H "Content-Type: application/json" \
  -d '{ "installation_id": "3f0c9a2e-6b1d-4c8e-9f7a-2d5e8b1c4a90" }'
200 OK
{
  "unsubscribed": true
}
Mobile app

Signaler l’ouverture d’une notification

POST/sdk/eventsApp Key — application mobile

Quand l’utilisateur touche une notification. Comptée une fois par appareil et par message, affichée dans le compteur « ouvertes ».

ChampDescription
type"opened"obligatoire
Type d’événement.
message_idULIDobligatoire
Le kd_message_id du payload.
installation_iduuidobligatoire
Id d’installation de l’appareil.
Request
curl -X POST https://push.kd-app.com/api/v1/sdk/events \
  -H "X-KD-App-Key: kdp_app_…" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "opened",
    "message_id": "01JB2QA4N7C2X9V5B8M3K6P1RD",
    "installation_id": "3f0c9a2e-6b1d-4c8e-9f7a-2d5e8b1c4a90"
  }'
202 Accepted
{
  "accepted": true
}
Web

Notifications du site (navigateurs)

Les mêmes notifications atteignent les visiteurs de votre site dans leur navigateur — sans application, sans Firebase, sans certificats : chaque application a sa propre clé VAPID, créée par KD Push. Fonctionne dans Chrome, Edge, Firefox, Samsung Internet et Safari sur Mac, et sur iPhone et iPad une fois le site ajouté à l’écran d’accueil (iOS 16.4+).

  1. Dans le portail marchand : votre application → Web, activez les notifications du site et ajoutez votre domaine (ou depuis votre serveur avec PUT /app/web).
  2. Créez /kdpush-sw.js à la racine de votre site avec cette seule ligne :
Node.js
importScripts('https://push.kd-app.com/sdk/web/v1/kdpush-sw.js');
  1. Ajoutez le script du SDK à chaque page, avant </head> :
Text
<script src="https://push.kd-app.com/sdk/web/v1/kdpush.js"
        data-app-key="kdp_app_…" async></script>

C’est tout : une petite carte invite les visiteurs à activer les notifications (son texte et son délai se règlent dans le portail), le navigateur est enregistré automatiquement et se manifeste une fois par jour. Un clic sur une notification ouvre son lien et compte comme une ouverture.

Options et API

Node.js
<!-- data-* options -->
data-app-key        required
data-service-worker /kdpush-sw.js (default)
data-prompt         "off" hides the built-in card
data-lang           ar | en | es | fr | de (default: the page language)
data-user-id        your user id   + data-user-signature (identity verification)
data-tags           JSON, e.g. '{"plan":"vip"}'

// JavaScript
KDPush.subscribe()            // ask (from a click) and subscribe → "subscribed" | "denied" | "default" | "unsupported" | "disabled"
KDPush.unsubscribe()
KDPush.isSubscribed()         // Promise<boolean>
KDPush.login('42', signature) // link the browser to your user
KDPush.logout()
KDPush.setTags({ plan: 'vip' })
Chaque navigateur envoie de lui-même le tag os (ios · android · desktop) : vous pouvez par exemple annoncer une application iPhone uniquement aux visiteurs sur iPhone : {"platforms":["web"],"audience":{"type":"tags","tags":{"os":"ios"}}}. Si votre site a déjà son propre service worker, ajoutez-y la ligne importScripts et faites pointer data-service-worker vers lui.
Web

Paramètres web

PUT/app/webServer Key — votre serveur

Pour une plateforme qui relie les sites de ses marchands : activez le web et définissez les domaines quand une boutique se connecte ou change de domaine. L’activation exige au moins un domaine et crée la clé VAPID de l’application. Les champs omis gardent leur valeur.

ChampDescription
enabledbooleanobligatoire
Active ou désactive le web.
originsstring[] ≤ 20selon le cas
Sites autorisés à abonner des visiteurs, p. ex. shop.example.com (enregistré en https://…).
icon_urlhttps URLfacultatif
Icône de chaque notification.
default_urlURLfacultatif
Destination du clic quand un message n’a pas de lien web.
prompt{enabled, delay_seconds, title, text}facultatif
La carte d’autorisation.
Request
curl -X PUT https://push.kd-app.com/api/v1/app/web \
  -H "Authorization: Bearer kdp_live_…" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true,"origins":["shop.example.com"],"icon_url":"https://cdn.example.com/logo.png"}'
200 OK
{
  "web": {
    "enabled": true,
    "origins": [
      "https://shop.example.com",
      "https://www.shop.example.com"
    ],
    "vapid_public_key": "BEl62iUYgUivxIkv69yViEuiBIa-Ib9-SkvMeAtA3LFgDzkrxZJjSgSnfckjBJuBkr3qBUYIHBQFLXYp5Nksh8U",
    "subscribers": 0,
    "icon_url": "https://cdn.example.com/logo.png",
    "default_url": null,
    "prompt": {
      "enabled": true,
      "delay_seconds": 6,
      "title": null,
      "text": null
    }
  }
}
Platform backend

Envoyer une notification

POST/messagesServer Key — votre serveur

Envoie (ou planifie) une notification aux seuls appareils de votre application. La réponse est immédiate avec le statut queued ; la livraison se fait en arrière-plan — suivez le statut et les compteurs avec Consulter une notification.

ChampDescription
bodystring ≤ 2000obligatoire
Texte de la notification.
titlestring ≤ 255facultatif
Titre.
urlstringfacultatif
Ouvert au toucher : un lien https:// ou un lien interne comme neonstore://app/1234.
image_urlhttps URLfacultatif
Grande image (Android ; iOS nécessite une extension dans l’application).
dataobject ≤ 30facultatif
Données supplémentaires, livrées sous forme de texte. Les clés commençant par kd_ sont réservées.
audienceobjectfacultatif
{"type":"all"} (par défaut) · users + external_user_ids · devices + installation_ids · tags + tags — jusqu’à 1000 ids.
platforms["ios","android","web"]facultatif
Limitez à certaines plateformes, ou omettez-le pour toutes.
scheduled_atISO 8601facultatif
Planifiez dans les 30 prochains jours.
ttl_secondsintfacultatif
Durée de conservation pour les appareils hors ligne (3 jours par défaut, 28 max.).
priorityhigh | normalfacultatif
high par défaut.
collapse_idstring ≤ 64facultatif
Une notification plus récente avec le même id remplace l’ancienne sur l’appareil.

En-têtes

ChampDescription
Idempotency-Keystring ≤ 128facultatif
Fortement recommandé. Pourquoi ?
Nécessite un abonnement marchand actif, sinon 402 subscription_inactive. Sans abonnement, rien ne fonctionne : ni configuration (/sdk/config), ni enregistrement d’appareils, ni modification des réglages de l’application depuis votre serveur.
Request
curl -X POST https://push.kd-app.com/api/v1/messages \
  -H "Authorization: Bearer kdp_live_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: app-created-1234" \
  -d '{
    "title": "تطبيق جديد",
    "body": "تمت إضافة واتساب الذهبي للمتجر",
    "url": "neonstore://app/1234",
    "image_url": "https://cdn.example.com/icons/1234.png",
    "data": { "app_id": "1234" },
    "audience": { "type": "all" }
  }'
202 Accepted
{
  "message": {
    "id": "01JB2QA4N7C2X9V5B8M3K6P1RD",
    "status": "queued",
    "source": "api",
    "title": "تطبيق جديد",
    "body": "تمت إضافة واتساب الذهبي للمتجر",
    "image_url": "https://cdn.example.com/icons/1234.png",
    "url": "neonstore://app/1234",
    "data": {
      "app_id": "1234"
    },
    "audience": {
      "type": "all"
    },
    "platforms": null,
    "scheduled_at": null,
    "counts": {
      "targeted": 0,
      "sent": 0,
      "failed": 0,
      "invalid": 0,
      "opened": 0
    },
    "error": null,
    "failures": [],
    "created_at": "2026-10-03T12:00:00+00:00",
    "started_at": null,
    "finished_at": null
  }
}
402 Payment Required
{
  "error": {
    "code": "subscription_inactive",
    "message": "No active KD Push subscription for this account."
  }
}
Platform backend

Consulter une notification

GET/messages/{id}Server Key — votre serveur

Statut et compteurs. Voir l’objet message pour chaque champ.

Request
curl https://push.kd-app.com/api/v1/messages/01JB2QA4N7C2X9V5B8M3K6P1RD \
  -H "Authorization: Bearer kdp_live_…"
200 OK
{
  "message": {
    "id": "01JB2QA4N7C2X9V5B8M3K6P1RD",
    "status": "sent",
    "source": "api",
    "title": "تطبيق جديد",
    "body": "تمت إضافة واتساب الذهبي للمتجر",
    "image_url": "https://cdn.example.com/icons/1234.png",
    "url": "neonstore://app/1234",
    "data": {
      "app_id": "1234"
    },
    "audience": {
      "type": "all"
    },
    "platforms": null,
    "scheduled_at": null,
    "counts": {
      "targeted": 1240,
      "sent": 1227,
      "failed": 3,
      "invalid": 10,
      "opened": 214
    },
    "error": null,
    "failures": [],
    "created_at": "2026-10-03T12:00:00+00:00",
    "started_at": "2026-10-03T12:00:01+00:00",
    "finished_at": "2026-10-03T12:00:04+00:00"
  }
}
Platform backend

Lister les notifications

GET/messagesServer Key — votre serveur

L’historique de votre application, du plus récent au plus ancien.

ChampDescription
statusstringfacultatif
scheduled · queued · sending · sent · failed · canceled
per_page1–100facultatif
20 par défaut.
Request
curl "https://push.kd-app.com/api/v1/messages?status=sent&per_page=20" \
  -H "Authorization: Bearer kdp_live_…"
200 OK
{
  "data": [
    {
      "id": "01JB2QA4N7C2X9V5B8M3K6P1RD",
      "status": "sent",
      "source": "api",
      "title": "تطبيق جديد",
      "body": "تمت إضافة واتساب الذهبي للمتجر",
      "image_url": "https://cdn.example.com/icons/1234.png",
      "url": "neonstore://app/1234",
      "data": {
        "app_id": "1234"
      },
      "audience": {
        "type": "all"
      },
      "platforms": null,
      "scheduled_at": null,
      "counts": {
        "targeted": 0,
        "sent": 0,
        "failed": 0,
        "invalid": 0,
        "opened": 0
      },
      "error": null,
      "failures": [],
      "created_at": "2026-10-03T12:00:00+00:00",
      "started_at": null,
      "finished_at": null
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 1,
    "last_page": 1
  }
}
Platform backend

Annuler une notification

POST/messages/{id}/cancelServer Key — votre serveur

Annule une notification planifiée, en file d’attente ou en cours d’envoi. Les lots pas encore envoyés s’arrêtent immédiatement.

Request
curl -X POST https://push.kd-app.com/api/v1/messages/01JB2QA4N7C2X9V5B8M3K6P1RD/cancel \
  -H "Authorization: Bearer kdp_live_…"
200 OK
{
  "message": {
    "id": "01JB2QA4N7C2X9V5B8M3K6P1RD",
    "status": "canceled",
    "source": "api",
    "title": "تطبيق جديد",
    "body": "تمت إضافة واتساب الذهبي للمتجر",
    "image_url": "https://cdn.example.com/icons/1234.png",
    "url": "neonstore://app/1234",
    "data": {
      "app_id": "1234"
    },
    "audience": {
      "type": "all"
    },
    "platforms": null,
    "scheduled_at": null,
    "counts": {
      "targeted": 0,
      "sent": 0,
      "failed": 0,
      "invalid": 0,
      "opened": 0
    },
    "error": null,
    "failures": [],
    "created_at": "2026-10-03T12:00:00+00:00",
    "started_at": null,
    "finished_at": null
  }
}
409 Conflict
{
  "error": {
    "code": "not_cancelable",
    "message": "Only scheduled, queued or sending messages can be canceled."
  }
}
Platform backend

Application et abonnement

GET/appServer Key — votre serveur

Vérifiez la clé et la possibilité d’envoyer avant d’activer les notifications automatiques : sending.can_send tient compte de l’abonnement et du quota du mois, et sending.reason donne la cause avec les codes de POST /messages (subscription_inactive · quota_exceeded). plan et usage montrent la situation du marchand ; portal renvoie là où il peut la corriger.

Request
curl https://push.kd-app.com/api/v1/app \
  -H "Authorization: Bearer kdp_live_…"
200 OK
{
  "app": {
    "id": "01JB2Q6V8K4Y3N5M7R9T1W0XZC",
    "name": "My Store",
    "platform": "neon",
    "status": "active",
    "ios_bundle_id": "com.store.app",
    "android_package": "com.store.app"
  },
  "subscription": {
    "status": "active",
    "expires_at": "2026-11-02T12:00:00+00:00",
    "grace_until": null,
    "days_left": 19,
    "can_send": true
  },
  "plan": {
    "name": "Pro",
    "period": "monthly",
    "monthly_messages": 100000,
    "max_devices": null,
    "max_apps": 3
  },
  "usage": {
    "deliveries_this_month": 18240,
    "remaining_this_month": 81760,
    "active_devices": 1240
  },
  "sending": {
    "can_send": true,
    "reason": null
  },
  "portal": {
    "plans": "https://push.kd-app.com/portal/plans",
    "subscription": "https://push.kd-app.com/portal/subscription"
  },
  "devices": {
    "active": 1240,
    "ios": 800,
    "android": 440,
    "web": 0
  },
  "web": {
    "enabled": false,
    "origins": [],
    "vapid_public_key": null,
    "subscribers": 0,
    "icon_url": null,
    "default_url": null,
    "prompt": {
      "enabled": true,
      "delay_seconds": 6,
      "title": null,
      "text": null
    }
  }
}
Platform backend

Package Android

PUT/app/androidServer Key — votre serveur

Pour une plateforme qui laisse son marchand choisir le package Android : envoyez le package avec lequel l’application de la boutique est compilée à chaque changement, pour que le portail l’affiche et que le fichier Firebase envoyé plus tard soit vérifié avec lui.

ChampDescription
packagestringobligatoire
Par exemple com.store.app.
Si le marchand a choisi son propre projet Firebase pour cette application et qu’il est enregistré pour un autre package, l’appel est refusé avec 409 package_mismatch et firebase_package indique celui qui est enregistré — pour que les notifications Android ne s’arrêtent jamais en silence.
Request
curl -X PUT https://push.kd-app.com/api/v1/app/android \
  -H "Authorization: Bearer kdp_live_…" \
  -H "Content-Type: application/json" \
  -d '{"package":"com.store.app"}'
200 OK
{
  "app": {
    "id": "01JB2Q6V8K4Y3N5M7R9T1W0XZC",
    "android_package": "com.store.app"
  }
}
409 Conflict
{
  "error": {
    "code": "package_mismatch",
    "message": "The app's Firebase project is registered for com.store.app; upload one for com.other.app first.",
    "firebase_package": "com.store.app"
  }
}
Platform backend

Statistiques des appareils

GET/devices/statsServer Key — votre serveur

Appareils actifs par plateforme.

Request
curl https://push.kd-app.com/api/v1/devices/stats \
  -H "Authorization: Bearer kdp_live_…"
200 OK
{
  "devices": {
    "active": 1240,
    "ios": 800,
    "android": 440,
    "web": 0
  }
}
Connect

Se connecter avec KD Push

Reliez la boutique d’un marchand à une application KD Push en un clic, sans que personne ne copie de clé : le marchand approuve sur une page KD Push et votre plateforme reçoit les clés de serveur à serveur (OAuth 2 authorization code avec PKCE).

  1. Nous enregistrons votre plateforme et vous donnons un client_id et un client_secret, avec les URI de redirection exactes autorisées.
  2. Envoyez le marchand vers /portal/connect/authorize avec les paramètres ci-dessous.
  3. Le marchand se connecte ou crée un compte (prérempli avec vos données), choisit ou crée l’application, puis autorise.
  4. Nous le renvoyons vers redirect_uri?code=…&state=… — ou error=access_denied s’il refuse.
  5. Votre serveur échange le code contre les clés avec POST /connect/token.

Paramètres d’autorisation

ChampDescription
response_typecodeobligatoire
Toujours code.
client_idstringobligatoire
L’id de votre plateforme.
redirect_urihttps URLobligatoire
Doit correspondre exactement à une URI enregistrée.
statestring ≤ 255obligatoire
Une valeur aléatoire que vous gardez et vérifiez au retour.
code_challengebase64urlobligatoire
BASE64URL(SHA256(code_verifier)) ; gardez le code_verifier (43–128 caractères) de votre côté.
code_challenge_methodS256obligatoire
Toujours S256.
app_namestring ≤ 120facultatif
Nom d’une nouvelle application si le marchand n’en a pas.
ios_bundle_idstringfacultatif
L’application correspondante est proposée en premier et l’id est enregistré auprès d’Apple automatiquement.
android_packagestringfacultatif
Le package avec lequel l’application Android est compilée ; une application existante qui n’en a pas l’apprend.
external_refstring ≤ 191facultatif
Votre id pour la boutique ; reconnecter la même boutique remplace sa clé précédente.
external_labelstring ≤ 255facultatif
Ce que voit le marchand (p. ex. le domaine de la boutique).
hint_name · hint_email · hint_company · hint_phonestringfacultatif
Préremplissent le formulaire d’inscription.
Vérifiez d’abord state. Le code vit 10 minutes et ne sert qu’une fois ; le présenter deux fois révoque ce qu’il a produit. Ni le code_verifier ni le client_secret n’atteignent jamais le navigateur.
Request
// 1. Send the merchant to KD Push (keep $verifier and $state in their session)
$verifier = Str::random(64);
$state = Str::random(40);
session(['kdpush' => compact('verifier', 'state')]);

return redirect('https://push.kd-app.com/portal/connect/authorize?' . http_build_query([
    'response_type' => 'code',
    'client_id' => config('services.kdpush.connect_client_id'),
    'redirect_uri' => 'https://your-platform.example/kdpush/callback',
    'state' => $state,
    'code_challenge' => rtrim(strtr(base64_encode(hash('sha256', $verifier, true)), '+/', '-_'), '='),
    'code_challenge_method' => 'S256',
    'app_name' => $store->name,           // optional: prefills a new app
    'ios_bundle_id' => $store->bundleId,  // optional
    'android_package' => $store->androidPackage, // optional
    'external_ref' => $store->id,         // optional: your id for the store
    'hint_email' => $owner->email,        // optional: prefills sign-up
], '', '&', PHP_QUERY_RFC3986));
Connect

Échanger le code

POST/connect/tokenclient_id + client_secret
ChampDescription
grant_typeauthorization_codeobligatoire
Fixe.
client_id · client_secretstringobligatoire
Les identifiants de votre plateforme.
codestringobligatoire
Issu de la redirection.
redirect_uristringobligatoire
La même URI que la requête.
code_verifierstringobligatoire
La valeur que vous avez gardée.

Vous recevez l’app_key (pour l’application), une nouvelle server_key émise pour cette connexion (affichée une seule fois), l’identity_secret et la situation du marchand dans account — au même format que GET /app.

La Server Key est la connexion : la révoquer d’un côté ou de l’autre met fin au lien, et reconnecter la même boutique la remplace au lieu d’accumuler les clés.
Request
curl -X POST https://push.kd-app.com/api/v1/connect/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "kdp_cli_…",
    "client_secret": "kdp_cs_…",
    "code": "kdp_code_…",
    "redirect_uri": "https://your-platform.example/kdpush/callback",
    "code_verifier": "…"
  }'
200 OK
{
  "connection": {
    "id": "01JB2QX5V8K4Y3N5M7R9T1W0XZ",
    "connected_at": "2026-10-14T12:00:00+00:00"
  },
  "app": {
    "id": "01JB2Q6V8K4Y3N5M7R9T1W0XZC",
    "name": "My Store",
    "platform": "neon",
    "ios_bundle_id": "com.store.app",
    "android_package": null
  },
  "merchant": {
    "name": "Store owner",
    "email": "[email protected]"
  },
  "app_key": "kdp_app_…",
  "server_key": "kdp_live_…",
  "identity_secret": "…",
  "api_base": "https://push.kd-app.com/api/v1",
  "account": {
    "subscription": {
      "status": "active",
      "expires_at": "2026-11-02T12:00:00+00:00",
      "grace_until": null,
      "days_left": 19,
      "can_send": true
    },
    "plan": {
      "name": "Pro",
      "period": "monthly",
      "monthly_messages": 100000,
      "max_devices": null,
      "max_apps": 3
    },
    "usage": {
      "deliveries_this_month": 18240,
      "remaining_this_month": 81760,
      "active_devices": 1240
    },
    "sending": {
      "can_send": true,
      "reason": null
    },
    "portal": {
      "plans": "https://push.kd-app.com/portal/plans",
      "subscription": "https://push.kd-app.com/portal/subscription"
    }
  }
}
400 Bad Request
{
  "error": {
    "code": "invalid_grant",
    "message": "Authorization code already used."
  }
}
Connect

Déconnecter

POST/connect/revokeServer Key — votre serveur

La Server Key envoyée cesse immédiatement de fonctionner ; l’application, ses appareils et son historique restent au marchand. Le marchand peut aussi se déconnecter depuis son panneau — votre clé répond alors 401 invalid_server_key ; traitez cela comme une déconnexion.

Request
curl -X POST https://push.kd-app.com/api/v1/connect/revoke \
  -H "Authorization: Bearer kdp_live_…"
200 OK
{
  "revoked": true
}
Reference

L’objet message

scheduled→queued→sending→sent·failedcanceled
ChampDescription
idULIDfacultatif
Id de la notification.
statusstringfacultatif
sent dès qu’au moins un appareil l’a acceptée, failed si aucun ne l’a fait (raison dans error).
sourceapi | portal | adminfacultatif
D’où elle a été envoyée.
counts.targetedintfacultatif
Appareils ciblés.
counts.sentintfacultatif
Acceptées par Apple/Google pour livraison.
counts.invalidintfacultatif
Appareils morts (application désinstallée, …) — exclus automatiquement ensuite.
counts.failedintfacultatif
Échecs pour d’autres raisons après les nouvelles tentatives.
counts.openedintfacultatif
Ouvertures signalées.
failures[{provider, code, http_status, count}]facultatif
Pourquoi certains appareils ne l’ont pas reçue, les plus fréquents d’abord : le code d’Apple ou de Firebase tel quel (p. ex. TopicDisallowed), le statut HTTP et le nombre d’appareils.
created_at · started_at · finished_at · scheduled_atISO 8601facultatif
Horodatages du cycle de vie.
Guide

Vérification de l’identité de l’utilisateur

Pour lier un appareil à un utilisateur (pour des notifications personnelles comme « votre abonnement se termine bientôt »), l’application a besoin d’une signature calculée par votre serveur après la connexion — sinon n’importe qui pourrait enregistrer son téléphone au nom d’un autre utilisateur et recevoir ses notifications.

Formula
user_signature = hex( HMAC-SHA256( external_user_id , identity_secret ) )
Sur votre serveur
// After the user logs in — on your backend, never in the app
$signature = hash_hmac('sha256', (string) $user->id, config('services.kdpush.identity_secret'));

return ['user_id' => $user->id, 'push_signature' => $signature];
Le secret se trouve dans le portail : application → Clés → Secret d’identité. Ne l’intégrez jamais dans l’application.
Guide

Ce que reçoit l’application

iOS — APNs
{
  "aps": {
    "alert": {
      "title": "تطبيق جديد",
      "body": "تمت إضافة واتساب الذهبي للمتجر"
    },
    "sound": "default",
    "mutable-content": 1
  },
  "kd_message_id": "01JB2QA4N7C2X9V5B8M3K6P1RD",
  "kd_url": "neonstore://app/1234",
  "kd_image": "https://cdn.example.com/icons/1234.png",
  "app_id": "1234"
}
Android — FCM
{
  "notification": {
    "title": "تطبيق جديد",
    "body": "تمت إضافة واتساب الذهبي للمتجر",
    "image": "https://cdn.example.com/icons/1234.png"
  },
  "data": {
    "kd_message_id": "01JB2QA4N7C2X9V5B8M3K6P1RD",
    "kd_url": "neonstore://app/1234",
    "app_id": "1234"
  }
}

Ouvrez kd_url au toucher et envoyez kd_message_id à /sdk/events. Les valeurs de data arrivent sous forme de texte.

Flutter
FirebaseMessaging.onMessageOpenedApp.listen((message) {
  final url = message.data['kd_url'];
  final id = message.data['kd_message_id'];
  if (url != null) openDeepLink(url);   // e.g. neonstore://app/1234
  if (id != null) reportOpened(id);     // POST /sdk/events
});
Guide

Liste de contrôle d’intégration de la plateforme

  1. 1Le marchand crée l’application dans le portail et colle l’App Key et la Server Key dans le panneau de votre plateforme.
  2. 2Votre plateforme compile l’application avec l’App Key intégrée — l’application appelle /sdk/config, puis enregistre l’appareil.
  3. 3Après la connexion, votre serveur remet à l’application une user_signature pour l’id de l’utilisateur.
  4. 4Votre plateforme envoie des notifications automatiques (nouvelle application, mise à jour) avec une Idempotency-Key par événement.
  5. 5Quand un catalogue est connecté pour la première fois, ne notifiez pas chaque application — envoyez un seul récapitulatif.
  6. 6Vérifiez subscription.can_send via /app avant d’activer les notifications automatiques.
Tools

Postman

Collection Postman prête à l’emploiv2.1Chaque requête avec exemples et réponses, l’authentification déjà configurée — ajoutez vos clés et lancez en quelques secondes.
  1. Téléchargez la collection et importez-la dans Postman (Import → choisissez le fichier).
  2. Dans les variables de la collection, renseignez app_key, server_key et un jeton d’appareil de test dans device_token.
  3. Lancez Register device, puis Send a notification — l’id est enregistré dans message_id pour les requêtes suivantes.
  4. Facultatif : renseignez external_user_id et identity_secret, et la collection calcule user_signature elle-même.