Commet
  • Pricing
Log InTry out
Introducción

Eventos de Suscripción

subscription.createdsubscription.activatedsubscription.canceledsubscription.updatedsubscription.plan_changed

Eventos de Pago

payment.receivedpayment.failed

Eventos de Recibo

invoice.created
DocumentaciónRecursosConstruir con AIAPI ReferenceWebhooks

Introducción

Recibe notificaciones en tiempo real cuando ocurren eventos en tu cuenta de Commet.

Los webhooks permiten que tu aplicación reciba notificaciones HTTP en tiempo real cuando ocurren eventos en Commet — como una suscripción que se activa, un pago que falla, o un recibo que se crea.

Cómo funciona

  1. Registra un endpoint URL en el dashboard de Commet
  2. Selecciona los eventos que quieres recibir
  3. Cuando ocurre un evento, Commet envía un request POST a tu URL con la data del evento

Estructura del payload

Cada webhook entrega un payload JSON con este envelope:

{
  "event": "subscription.activated",
  "timestamp": "2026-03-25T14:30:00.000Z",
  "organizationId": "org_abc123",
  "mode": "live",
  "apiVersion": "2026-05-01",
  "data": {
    // Campos específicos del evento
  }
}
CampoTipoDescripción
eventstringEl tipo de evento (ej. subscription.activated)
timestampstringFecha y hora ISO 8601 cuando se emitió el evento
organizationIdstringEl ID de tu organización
modestring"live" o "sandbox" — el entorno que disparó el evento
apiVersionstringLa versión de la API usada para armar este payload (ej. 2026-05-01). Ver Versionado de API
dataobjectPayload específico del evento — ver páginas individuales abajo

Manejo de webhooks

Recibe eventos exponiendo un endpoint HTTP. El SDK de Node.js incluye un handler dedicado para Next.js que verifica firmas y rutea eventos automáticamente; en otros lenguajes, verifica el payload y despacha según event.

app/api/webhooks/commet/route.ts
import { Webhooks } from "@commet/next"

export const POST = Webhooks({
  webhookSecret: process.env.COMMET_WEBHOOK_SECRET!,

  onSubscriptionActivated: async (payload) => {
    await db.update(users)
      .set({ isPaid: true })
      .where(eq(users.id, payload.data.customerId))
  },

  onSubscriptionCanceled: async (payload) => {
    await db.update(users)
      .set({ isPaid: false })
      .where(eq(users.id, payload.data.customerId))
  },

  onPayload: async (payload) => {
    console.log(`Received: ${payload.event}`)
  },
})
import os
from flask import Flask, request, Response
from commet import Commet

app = Flask(__name__)
commet = Commet(api_key=os.environ['COMMET_API_KEY'])

@app.post('/api/webhooks/commet')
def handle_webhook():
    payload = commet.webhooks.verify_and_parse(
        raw_body=request.get_data(as_text=True),
        signature=request.headers.get('x-commet-signature'),
        secret=os.environ['COMMET_WEBHOOK_SECRET'],
    )

    if payload is None:
        return Response('Invalid signature', status=401)

    if payload['event'] == 'subscription.activated':
        # Otorgar acceso
        pass
    elif payload['event'] == 'subscription.canceled':
        # Revocar acceso
        pass

    return Response('OK', status=200)
import (
    "io"
    "net/http"
    "os"

    "github.com/commet/commet-go"
)

client, _ := commet.New(os.Getenv("COMMET_API_KEY"))

http.HandleFunc("/api/webhooks/commet", func(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)

    payload, err := client.Webhooks.VerifyAndParse(
        string(body),
        r.Header.Get("X-Commet-Signature"),
        os.Getenv("COMMET_WEBHOOK_SECRET"),
    )
    if err != nil {
        http.Error(w, "Invalid signature", http.StatusUnauthorized)
        return
    }

    switch payload["event"] {
    case "subscription.activated":
        // Otorgar acceso
    case "subscription.canceled":
        // Revocar acceso
    }

    w.WriteHeader(http.StatusOK)
})
import co.commet.Commet;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
public class CommetWebhookController {

    private final Commet commet = Commet.builder()
        .apiKey(System.getenv("COMMET_API_KEY"))
        .build();

    @PostMapping("/api/webhooks/commet")
    public ResponseEntity<String> handle(
        @RequestBody String rawBody,
        @RequestHeader("X-Commet-Signature") String signature
    ) {
        Map<String, Object> payload = commet.webhooks().verifyAndParse(
            rawBody,
            signature,
            System.getenv("COMMET_WEBHOOK_SECRET")
        );

        if (payload == null) {
            return ResponseEntity.status(401).body("Invalid signature");
        }

        String event = (String) payload.get("event");
        switch (event) {
            case "subscription.activated" -> { /* Otorgar acceso */ }
            case "subscription.canceled" -> { /* Revocar acceso */ }
        }

        return ResponseEntity.ok("OK");
    }
}
use Commet\Commet;

$commet = new Commet(apiKey: getenv('COMMET_API_KEY'));

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_COMMET_SIGNATURE'] ?? null;

$payload = $commet->webhooks->verifyAndParse(
    $rawBody,
    $signature,
    getenv('COMMET_WEBHOOK_SECRET'),
);

if ($payload === null) {
    http_response_code(401);
    exit('Invalid signature');
}

match ($payload['event']) {
    'subscription.activated' => null, // Otorgar acceso
    'subscription.canceled' => null,  // Revocar acceso
    default => null,
};

http_response_code(200);
echo 'OK';

Verificando firmas manualmente

Si no usas @commet/next, verifica la firma HMAC-SHA256 con el SDK:

import { Commet } from "@commet/node"

const commet = new Commet({ apiKey: process.env.COMMET_API_KEY! })

export async function POST(request: Request) {
  const rawBody = await request.text()
  const signature = request.headers.get("x-commet-signature")

  const payload = commet.webhooks.verifyAndParse({
    rawBody,
    signature,
    secret: process.env.COMMET_WEBHOOK_SECRET!,
  })

  if (!payload) {
    return new Response("Invalid signature", { status: 403 })
  }

  switch (payload.event) {
    case "subscription.activated":
      // Otorgar acceso
      break
    case "subscription.canceled":
      // Revocar acceso
      break
  }

  return new Response("OK", { status: 200 })
}
import os
from flask import Flask, request, Response
from commet import Commet

app = Flask(__name__)
commet = Commet(api_key=os.environ['COMMET_API_KEY'])

@app.post('/webhooks/commet')
def commet_webhook():
    raw_body = request.get_data(as_text=True)
    signature = request.headers.get('x-commet-signature')

    payload = commet.webhooks.verify_and_parse(
        raw_body=raw_body,
        signature=signature,
        secret=os.environ['COMMET_WEBHOOK_SECRET'],
    )

    if payload is None:
        return Response('Invalid signature', status=403)

    if payload['event'] == 'subscription.activated':
        # Otorgar acceso
        pass
    elif payload['event'] == 'subscription.canceled':
        # Revocar acceso
        pass

    return Response('OK', status=200)
import (
    "io"
    "net/http"
    "os"

    "github.com/commet/commet-go"
)

client, _ := commet.New(os.Getenv("COMMET_API_KEY"))

http.HandleFunc("/webhooks/commet", func(w http.ResponseWriter, r *http.Request) {
    body, _ := io.ReadAll(r.Body)
    signature := r.Header.Get("X-Commet-Signature")

    payload, err := client.Webhooks.VerifyAndParse(
        string(body),
        signature,
        os.Getenv("COMMET_WEBHOOK_SECRET"),
    )
    if err != nil {
        http.Error(w, "Invalid signature", http.StatusForbidden)
        return
    }

    switch payload["event"] {
    case "subscription.activated":
        // Otorgar acceso
    case "subscription.canceled":
        // Revocar acceso
    }

    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
})
import co.commet.Commet;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
public class WebhookController {

    private final Commet commet = Commet.builder()
        .apiKey(System.getenv("COMMET_API_KEY"))
        .build();

    @PostMapping("/webhooks/commet")
    public ResponseEntity<String> handle(
        @RequestBody String rawBody,
        @RequestHeader("X-Commet-Signature") String signature
    ) {
        Map<String, Object> payload = commet.webhooks().verifyAndParse(
            rawBody,
            signature,
            System.getenv("COMMET_WEBHOOK_SECRET")
        );

        if (payload == null) {
            return ResponseEntity.status(403).body("Invalid signature");
        }

        String event = (String) payload.get("event");
        switch (event) {
            case "subscription.activated" -> { /* Otorgar acceso */ }
            case "subscription.canceled" -> { /* Revocar acceso */ }
        }

        return ResponseEntity.ok("OK");
    }
}
use Commet\Commet;

$commet = new Commet(apiKey: getenv('COMMET_API_KEY'));

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_COMMET_SIGNATURE'] ?? null;

$payload = $commet->webhooks->verifyAndParse(
    $rawBody,
    $signature,
    getenv('COMMET_WEBHOOK_SECRET'),
);

if ($payload === null) {
    http_response_code(403);
    echo 'Invalid signature';
    exit;
}

match ($payload['event']) {
    'subscription.activated' => null, // Otorgar acceso
    'subscription.canceled' => null,  // Revocar acceso
    default => null,
};

http_response_code(200);
echo 'OK';

Headers

Commet envía estos headers con cada request de webhook:

HeaderDescripción
X-Commet-SignatureFirma HMAC-SHA256 en hex del body crudo
X-Commet-EventEl tipo de evento (ej. subscription.activated)
X-Commet-TimestampFecha y hora ISO 8601 cuando se emitió el evento
Content-Typeapplication/json

Política de reintentos

Si tu endpoint devuelve un status code distinto de 2xx o hace timeout (10 segundos), Commet reintenta con backoff exponencial:

IntentoDemora
1er reintento1 minuto
2do reintento5 minutos
3er reintento15 minutos
4to reintento30 minutos
5to reintento1 hora
6to reintento2 horas
7mo reintento4 horas
8vo reintento6 horas

Después de 8 intentos fallidos, la entrega se marca como fallida y enviamos un email al destinatario de notificaciones de tu organización con la URL del endpoint, el tipo de evento, y la última response que recibimos (status code HTTP o código de error).

Auto-desactivación de endpoints rotos

Si tres eventos consecutivos fallan al entregarse, Commet desactiva automáticamente el endpoint para que deje de consumir reintentos. Vas a recibir un segundo email confirmando que el endpoint fue desactivado.

Una vez que arregles el problema en tu receptor, reactiva el endpoint desde el dashboard de Commet en Settings → Webhooks → Endpoints. Los eventos que llegaron mientras el endpoint estaba desactivado no se reenvían automáticamente — contacta a soporte si necesitas recuperar eventos perdidos.

Puedes monitorear el estado de entrega, inspeccionar payloads y reintentar entregas individuales desde el dashboard en cualquier momento.

Ciclo de vida del status de suscripción

Cada webhook subscription.* incluye un campo status. Estos son los valores válidos y cuáles otorgan acceso a tu producto:

Status¿Otorga acceso?Significado
draftNoEstado interno de setup antes de que se dispare cualquier evento
pending_paymentNoSuscripción creada, esperando que el primer cobro confirme
trialingSíTrial activo; tarjeta capturada, sin cobro todavía
activeSíPaga y al día
past_dueSí (período de gracia)Pago fallido; reintentos automáticos en curso
pausedNoPausado por admin; sin cobros
canceledNoCliente o admin canceló
expiredNoEl trial terminó sin pago, o estado terminal

Flujo típico:

draft → pending_payment → trialing → active → canceled
                                  ↓
                                paused → past_due → expired

Regla práctica: condiciona el acceso con status === "active" || status === "trialing". Apóyate en subscription.activated para activar acceso y en subscription.canceled para revocarlo.

Relacionado

  • Eventos de Webhook — todos los eventos y sus payloads

¿Cómo está esta guía?

subscription.created

Se dispara cuando se crea una nueva suscripción

En esta página

Cómo funciona
Estructura del payload
Manejo de webhooks
Verificando firmas manualmente
Headers
Política de reintentos
Auto-desactivación de endpoints rotos
Ciclo de vida del status de suscripción
Relacionado