API Reference · v1

Envía notificaciones push a tus clientes con una sola petición

Una clave pública dentro de la app registra los dispositivos y una clave secreta en tu servidor envía. Nosotros nos encargamos de Apple y Google, del enrutamiento, los reintentos y la limpieza de dispositivos inactivos.

Empieza en 3 pasos
Introduction

Introducción

KD Push es un servicio de notificaciones push totalmente gestionado para plataformas y sus comerciantes. Los comerciantes nunca tocan OneSignal, Firebase ni certificados de Apple: añaden su app en el portal, toman una App Key y una Server Key, y su plataforma envía a través de esta API.

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

Todas las peticiones y respuestas son JSON sobre HTTPS, las fechas son ISO 8601 y los ids públicos son ULID.

Quick start

Inicio rápido

  1. 1
    Crea tu app y toma sus claves
    En el portal del comerciante: Nueva app → copia la App Key y la Server Key (se muestra una vez).
  2. 2
    Registra el dispositivo desde la app
    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
    Envía desde tu servidor
    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

Autenticación

Cada app tiene dos tipos de claves, y cada una tiene un único lugar:

App Key
kdp_app_…
X-KD-App-Key: kdp_app_…
Dónde:
Dentro de la app móvil (pública)
Puede:
Registrar dispositivos, leer la configuración, registrar aperturas
Nunca puede enviar
Server Key
kdp_live_…
Authorization: Bearer kdp_live_…
Dónde:
Solo en el servidor de tu plataforma (secreta)
Puede:
Enviar, seguir, cancelar, estadísticas
Se muestra una vez: solo guardamos un hash
La clave decide la app. Ningún endpoint acepta un id de app o de comerciante en el cuerpo, así que la clave de una app nunca puede llegar a los dispositivos o mensajes de otra: simplemente recibe 404.
Errors

Errores

Todos los errores tienen la misma forma y nunca revelan detalles internos:

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 desconocida o app desactivada.
401invalid_server_key
Server Key desconocida o revocada.
402subscription_inactive
El comerciante no tiene una suscripción activa.
402quota_exceeded
Se agotó la cuota mensual de notificaciones del plan.
403invalid_user_signature
La firma no coincide con external_user_id.
403web_disabled · origin_not_allowed
La web está desactivada para la app o la página no está en uno de sus sitios.
422invalid_subscription · provider_mismatch
Claves de suscripción del navegador inservibles, o plataforma y proveedor que no coinciden.
403app_disabled · account_banned
App desactivada o cuenta bloqueada.
404not_found
No encontrado, o pertenece a otra app.
409not_cancelable
La notificación ya está en un estado final.
422validation_failed
Datos no válidos; los detalles están en fields.
422invalid_token
Token de dispositivo con formato incorrecto.
429rate_limited
Demasiadas peticiones; espera según Retry-After.
500server_error
Error interno: vuelve a intentarlo más tarde.
Rate limits

Límites de uso

60
pet. / min
Endpoints de la app, por IP
20,000
pet. / min
Endpoints de la app, por app
600
pet. / min
Endpoints de servidor, por Server Key

Si superas el límite recibes 429 rate_limited con la cabecera Retry-After. Enviar a muchos dispositivos es una sola petición: el motor la reparte internamente.

Idempotency

Idempotencia

Envía una cabecera Idempotency-Key con cada envío (por ejemplo, el id de tu evento). Repetir la petición con la misma clave en 24 h devuelve la misma notificación con 200 en lugar de crear una nueva, así que reintentar tras un fallo de red siempre es seguro.

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

Configuración

GET/sdk/configApp Key — app móvil

Pídela al iniciar la app. Android toma de aquí sus opciones de Firebase, así que cambiar de proyecto de Firebase o de proveedor nunca obliga a recompilar la app.

Se puede almacenar en caché: la respuesta lleva 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

Registrar un dispositivo

POST/sdk/devicesApp Key — app móvil

Llámalo en cada inicio de la app, renovación del token, inicio y cierre de sesión. 201 la primera vez y 200 después.

CampoDescripción
platformios | android | webobligatorio
Plataforma del dispositivo.
tokenstringobligatorio
Token de APNs (hex) o token de registro de FCM; para un navegador, el endpoint de la suscripción.
installation_iduuidopcional
Un id estable que generas una vez y guardas en la app por cada App Key. Vincula las renovaciones del token al mismo dispositivo.
providerapns | fcm | webpushopcional
Si se omite, se deduce de la plataforma.
ios.bundle_idstringopcional
El valor de Bundle.main.bundleIdentifier.
ios.profile_app_idstringopcional
application-identifier de embedded.mobileprovision, sin el prefijo del equipo.
ios.team_idstring(10)opcional
TeamIdentifier del mismo archivo.
ios.environmentproduction | sandboxopcional
El valor de aps-environment.
android.fcm_project_idstringopcional
El proyecto de Firebase que emitió el token (de /sdk/config).
web.p256dh · web.authbase64urlsegún el caso
Las claves de la suscripción del navegador (de PushSubscription.toJSON()). Obligatorias para web; solo se aceptan desde una página de uno de los sitios de la app.
external_user_idstring | nullopcional
Tu id de usuario. Envía null al cerrar sesión.
user_signaturehexsegún el caso
Obligatorio con external_user_id mientras la verificación de identidad esté activa. Cómo calcularla
tagsobjectopcional
Hasta 20 etiquetas para segmentar; claves [A-Za-z0-9_.-] y valores escalares cortos.
locale · timezone · app_version · os_version · device_model · sdk_versionstringopcional
Se muestra en el portal del comerciante.
Un token pertenece a una sola app. Si otra app lo registra (p. ej. el APK de otra tienda instalado encima del tuyo), el dispositivo pasa a ella y se borran su usuario y sus etiquetas: tus notificaciones nunca llegan al cliente de otra tienda.
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

Cancelar suscripción

POST/sdk/devices/unsubscribeApp Key — app móvil

Cuando el usuario desactiva las notificaciones dentro de la app. El dispositivo deja de recibirlas hasta que se registre de nuevo.

CampoDescripción
installation_iduuidobligatorio
El mismo id usado al registrarse.
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

Registrar la apertura de una notificación

POST/sdk/eventsApp Key — app móvil

Cuando el usuario toca una notificación. Se cuenta una vez por dispositivo y mensaje, y aparece en el contador «abiertas».

CampoDescripción
type"opened"obligatorio
Tipo de evento.
message_idULIDobligatorio
El kd_message_id del payload.
installation_iduuidobligatorio
Id de instalación del dispositivo.
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

Notificaciones del sitio (navegadores)

Las mismas notificaciones llegan a los visitantes de tu sitio en su navegador, sin app, sin Firebase y sin certificados: cada app tiene su propia clave VAPID, creada por KD Push. Funciona en Chrome, Edge, Firefox, Samsung Internet y Safari en Mac, y en iPhone y iPad una vez añadido el sitio a la pantalla de inicio (iOS 16.4+).

  1. En el portal del comerciante: tu app → Web, activa las notificaciones del sitio y añade tu dominio (o desde tu servidor con PUT /app/web).
  2. Crea /kdpush-sw.js en la raíz de tu sitio con esta única línea:
Node.js
importScripts('https://push.kd-app.com/sdk/web/v1/kdpush-sw.js');
  1. Añade el script del SDK a cada página, antes de </head>:
Text
<script src="https://push.kd-app.com/sdk/web/v1/kdpush.js"
        data-app-key="kdp_app_…" async></script>

Y listo: una pequeña tarjeta invita a los visitantes a activar las notificaciones (su texto y su retraso se configuran en el portal), el navegador se registra automáticamente y se reporta una vez al día. Al hacer clic en una notificación se abre su enlace y cuenta como abierta.

Opciones y 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' })
Cada navegador envía por sí solo la etiqueta os (ios · android · desktop), así que puedes, por ejemplo, anunciar una app de iPhone solo a los visitantes con iPhone: {"platforms":["web"],"audience":{"type":"tags","tags":{"os":"ios"}}}. Si tu sitio ya tiene su propio service worker, añádele la línea importScripts y apunta data-service-worker a él.
Web

Ajustes web

PUT/app/webServer Key — tu servidor

Para una plataforma que vincula los sitios de sus comerciantes: activa la web y define los dominios cuando una tienda se conecta o cambia de dominio. Activarla requiere al menos un dominio y crea la clave VAPID de la app. Los campos omitidos conservan su valor.

CampoDescripción
enabledbooleanobligatorio
Activa o desactiva la web.
originsstring[] ≤ 20según el caso
Sitios autorizados a suscribir visitantes, p. ej. shop.example.com (se guarda como https://…).
icon_urlhttps URLopcional
Icono de cada notificación.
default_urlURLopcional
Destino del clic cuando un mensaje no tiene enlace web.
prompt{enabled, delay_seconds, title, text}opcional
La tarjeta de permiso.
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

Enviar una notificación

POST/messagesServer Key — tu servidor

Envía (o programa) una notificación solo a los dispositivos de tu app. La respuesta es inmediata con el estado queued; la entrega se hace en segundo plano: sigue el estado y los contadores con Consultar una notificación.

CampoDescripción
bodystring ≤ 2000obligatorio
Texto de la notificación.
titlestring ≤ 255opcional
Título.
urlstringopcional
Se abre al tocar: un enlace https:// o un enlace interno como neonstore://app/1234.
image_urlhttps URLopcional
Imagen grande (Android; iOS necesita una extensión en la app).
dataobject ≤ 30opcional
Datos extra, entregados como texto. Las claves que empiezan por kd_ están reservadas.
audienceobjectopcional
{"type":"all"} (por defecto) · users + external_user_ids · devices + installation_ids · tags + tags — hasta 1000 ids.
platforms["ios","android","web"]opcional
Limita a algunas plataformas u omítelo para todas.
scheduled_atISO 8601opcional
Programa dentro de los próximos 30 días.
ttl_secondsintopcional
Cuánto tiempo se guarda para dispositivos sin conexión (3 días por defecto, máx. 28).
priorityhigh | normalopcional
Por defecto high.
collapse_idstring ≤ 64opcional
Una notificación más reciente con el mismo id sustituye a la anterior en el dispositivo.

Cabeceras

CampoDescripción
Idempotency-Keystring ≤ 128opcional
Muy recomendable. ¿Por qué?
Requiere una suscripción activa del comerciante; si no, 402 subscription_inactive. Sin ella no funciona nada: ni la configuración (/sdk/config), ni el registro de dispositivos, ni los cambios de ajustes de la app desde tu servidor.
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

Consultar una notificación

GET/messages/{id}Server Key — tu servidor

Estado y contadores. Consulta el objeto mensaje para cada campo.

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

Listar notificaciones

GET/messagesServer Key — tu servidor

El historial de tu app, del más reciente al más antiguo.

CampoDescripción
statusstringopcional
scheduled · queued · sending · sent · failed · canceled
per_page1–100opcional
20 por defecto.
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

Cancelar una notificación

POST/messages/{id}/cancelServer Key — tu servidor

Cancela una notificación programada, en cola o en envío. Los lotes aún no enviados se detienen de inmediato.

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

App y suscripción

GET/appServer Key — tu servidor

Comprueba la clave y si es posible enviar antes de activar las notificaciones automáticas: sending.can_send tiene en cuenta la suscripción y la cuota de este mes, y sending.reason indica la causa con los mismos códigos que POST /messages (subscription_inactive · quota_exceeded). plan y usage muestran la situación del comerciante; portal enlaza a donde puede solucionarla.

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

Paquete de Android

PUT/app/androidServer Key — tu servidor

Para una plataforma que permite a su comerciante elegir el paquete de Android: envía el paquete con el que se compila la app de la tienda cada vez que cambie, así el portal lo muestra y el archivo de Firebase que se suba después se comprueba con él.

CampoDescripción
packagestringobligatorio
Por ejemplo com.store.app.
Si el comerciante eligió su propio proyecto de Firebase para esta app y está registrado para otro paquete, la llamada se rechaza con 409 package_mismatch y firebase_package indica el registrado, para que las notificaciones de Android nunca se detengan sin aviso.
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

Estadísticas de dispositivos

GET/devices/statsServer Key — tu servidor

Dispositivos activos por plataforma.

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

Conectar con KD Push

Vincula la tienda de un comerciante a una app de KD Push con un clic, sin que nadie copie una clave: el comerciante aprueba en una página de KD Push y tu plataforma recibe las claves de servidor a servidor (OAuth 2 authorization code con PKCE).

  1. Registramos tu plataforma y te damos un client_id y un client_secret, con las URI de redirección exactas permitidas.
  2. Envía al comerciante a /portal/connect/authorize con los parámetros de abajo.
  3. El comerciante inicia sesión o crea una cuenta (con los datos que enviaste), elige o crea la app y autoriza.
  4. Lo devolvemos a redirect_uri?code=…&state=…, o a error=access_denied si lo rechaza.
  5. Tu servidor canjea el código por las claves con POST /connect/token.

Parámetros de autorización

CampoDescripción
response_typecodeobligatorio
Siempre code.
client_idstringobligatorio
El id de tu plataforma.
redirect_urihttps URLobligatorio
Debe coincidir exactamente con una URI registrada.
statestring ≤ 255obligatorio
Un valor aleatorio que guardas y compruebas a la vuelta.
code_challengebase64urlobligatorio
BASE64URL(SHA256(code_verifier)); guarda el code_verifier (43–128 caracteres) en tu lado.
code_challenge_methodS256obligatorio
Siempre S256.
app_namestring ≤ 120opcional
Nombre para una app nueva si el comerciante no tiene ninguna.
ios_bundle_idstringopcional
La app correspondiente se ofrece primero y el id se registra en Apple automáticamente.
android_packagestringopcional
El paquete con el que se compila la app de Android; una app existente sin paquete lo aprende.
external_refstring ≤ 191opcional
Tu id para la tienda; reconectar la misma tienda sustituye su clave anterior.
external_labelstring ≤ 255opcional
Lo que ve el comerciante (p. ej. el dominio de la tienda).
hint_name · hint_email · hint_company · hint_phonestringopcional
Rellenan el formulario de registro.
Comprueba primero state. El código vale 10 minutos y una sola vez; presentarlo dos veces revoca lo que produjo. Ni el code_verifier ni el client_secret llegan nunca al navegador.
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

Canjear el código

POST/connect/tokenclient_id + client_secret
CampoDescripción
grant_typeauthorization_codeobligatorio
Fijo.
client_id · client_secretstringobligatorio
Las credenciales de tu plataforma.
codestringobligatorio
De la redirección.
redirect_uristringobligatorio
La misma URI que usó la petición.
code_verifierstringobligatorio
El valor que guardaste.

Recibes la app_key (para la app), una server_key nueva emitida para esta conexión (se muestra una vez), el identity_secret y la situación del comerciante en account, con la misma forma que GET /app.

La Server Key es la conexión: revocarla desde cualquiera de los dos lados termina el vínculo, y reconectar la misma tienda la sustituye en lugar de acumular claves.
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

Desconectar

POST/connect/revokeServer Key — tu servidor

La Server Key que envías deja de funcionar al instante; la app, sus dispositivos y su historial se quedan con el comerciante. El comerciante también puede desconectar desde su panel: tu clave responde entonces 401 invalid_server_key; trátalo como desconectado.

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

El objeto mensaje

scheduled→queued→sending→sent·failedcanceled
CampoDescripción
idULIDopcional
Id de la notificación.
statusstringopcional
sent en cuanto al menos un dispositivo la acepta, failed si no la acepta ninguno (motivo en error).
sourceapi | portal | adminopcional
Desde dónde se envió.
counts.targetedintopcional
Dispositivos destinatarios.
counts.sentintopcional
Aceptadas por Apple/Google para su entrega.
counts.invalidintopcional
Dispositivos inactivos (app desinstalada, …): se excluyen automáticamente después.
counts.failedintopcional
Fallidas por otros motivos tras los reintentos.
counts.openedintopcional
Aperturas registradas.
failures[{provider, code, http_status, count}]opcional
Por qué algunos dispositivos no la recibieron, los más frecuentes primero: el código de Apple o Firebase tal cual (p. ej. TopicDisallowed), el estado HTTP y cuántos dispositivos.
created_at · started_at · finished_at · scheduled_atISO 8601opcional
Marcas de tiempo del ciclo de vida.
Guide

Verificación de la identidad del usuario

Para vincular un dispositivo a un usuario (para notificaciones personales como «tu suscripción vence pronto»), la app necesita una firma calculada por tu servidor tras el inicio de sesión; si no, cualquiera podría registrar su teléfono como otro usuario y recibir sus notificaciones.

Formula
user_signature = hex( HMAC-SHA256( external_user_id , identity_secret ) )
En tu servidor
// 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];
El secreto está en el portal: app → Claves → Secreto de identidad. Nunca lo incluyas dentro de la app.
Guide

Lo que recibe la app

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"
  }
}

Abre kd_url al tocar y envía kd_message_id a /sdk/events. Los valores de data llegan como texto.

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

Lista de integración de la plataforma

  1. 1El comerciante crea la app en el portal y pega la App Key y la Server Key en el panel de tu plataforma.
  2. 2Tu plataforma compila la app con la App Key dentro: la app llama a /sdk/config y luego registra el dispositivo.
  3. 3Tras el inicio de sesión, tu servidor entrega a la app una user_signature para el id del usuario.
  4. 4Tu plataforma envía notificaciones automáticas (app nueva, actualización) con una Idempotency-Key por evento.
  5. 5Cuando se conecta un catálogo por primera vez, no notifiques por cada app: envía un solo resumen.
  6. 6Comprueba subscription.can_send con /app antes de activar las notificaciones automáticas.
Tools

Postman

Colección de Postman lista para usarv2.1Todas las peticiones con ejemplos y respuestas, y la autenticación ya configurada: añade tus claves y pruébalas en segundos.
  1. Descarga la colección e impórtala en Postman (Import → elige el archivo).
  2. En las variables de la colección pon app_key, server_key y un token de dispositivo de prueba en device_token.
  3. Ejecuta Register device y luego Send a notification: el id se guarda en message_id para las peticiones siguientes.
  4. Opcional: pon external_user_id e identity_secret y la colección calcula user_signature por sí sola.