Se você administra uma empresa que depende de reservas, provavelmente precisará dos dados da sua reserva em mais de um lugar. Seu CRM precisa saber quando um novo cliente faz uma reserva. Seu ERP precisa dos dados de receita. Sua ferramenta de marketing precisa acionar um e-mail de boas-vindas. Seu aplicativo personalizado precisa criar reservas de maneira programática.
A API pública e os Webhooks do Cowlendar tornam tudo isso possível. A API permite que você leia seus serviços e reservas e crie novas reservas a partir de qualquer sistema externo. Os webhooks enviam notificações em tempo real para o seu servidor sempre que algo acontece: uma reserva é criada, confirmada, cancelada, reprogramada ou ocorre um evento de assinatura.
Ambos os recursos estão atualmente em Beta.
O que você pode construir com a API Cowlendar?
Aqui estão cenários reais de integração que os comerciantes Cowlendar estão construindo agora:
Sincronize todas as reservas com HubSpot, Salesforce ou qualquer CRM. Quando um cliente reserva um corte de cabelo, uma aula de ioga ou um aluguel, um webhook é acionado instantaneamente com os dados completos da reserva (nome, e-mail, telefone, serviço, data, preço, membro da equipe). Seu CRM cria ou atualiza o contato automaticamente, sem entrada manual de dados. Crie sua própria página de reservas ou app móvel. Use a API para buscar seus serviços, incluindo durações, membros da equipe, equipamentos e categorias de participantes, e crie reservas em seu próprio front-end. A reserva aciona os mesmos e-mails de confirmação, notificações por SMS e sincronização Google Calendar que qualquer reserva feita através do widget Cowlendar. Automatize fluxos de trabalho com Zapier ou Make sem código. Aponte um webhook Cowlendar para seu URL de webhook Zapier ou Make. Cada evento de reserva se torna um gatilho. Envie uma notificação do Slack quando alguém fizer uma reserva, adicione uma linha ao Planilhas Google, crie um cartão Trello ou acione uma sequência de e-mail no Klaviyo ou Mailchimp. Alimente os dados de reserva em seu relatório ou ferramenta de BI. Use o endpoint Listar Reservas para extrair todas as reservas dentro de um intervalo de datas, filtradas por status, serviço, presença ou e-mail do cliente. Exporte para seu data warehouse, Planilhas Google ou Airtable para painéis personalizados. Vincule reservas a créditos de assinatura. Ao criar uma reserva via API, passe um subscription_id para deduzir automaticamente um crédito do plano do cliente, por exemplo, um passe de ioga de 10 aulas. Conecte reservas de PDV ou de administrador a sistemas externos. Os webhooks são acionados para todas as fontes de reservas: o widget da vitrine, o painel de administração, o PDV e a própria API. Nada passa despercebido.
Autenticação
Cada solicitação de API requer um token Bearer gerado pelo seu administrador Cowlendar. Os tokens têm acesso total de leitura e gravação às reservas e serviços da sua loja. Trate-as como senhas: não as submeta ao controle de versão nem as compartilhe em canais públicos.
Como gerar seu token de API
Passo 1: No administrador do Shopify, abra o aplicativo Cowlendar.
Passo 2: Vá para Settings > Public API.
Passo 3: Clique em Generate token.
Passo 4: Dê ao seu token um nome descritivo para que você possa identificá-lo posteriormente, por exemplo "HubSpot sync", "Zapier" ou "Mobile app".
Passo 5: Clique em Generate . Cowlendar mostra o token completo apenas uma vez. Copie-o imediatamente e armazene-o em um local seguro, como um gerenciador de senhas ou nas variáveis de ambiente do seu servidor.
Você pode revogar qualquer token a qualquer momento na mesma página. A revogação de um token interrompe imediatamente todas as solicitações de API que o utilizam.
Usando seu token
Inclua o token no cabeçalho Authorization de cada solicitação de API:
Authorization: Bearer YOUR_TOKEN_HERE
Exemplo com ondulação:
curl https://app.cowlendar.com/public-api/v1/services -H "Authorization: Bearer YOUR_TOKEN_HERE"
Se o token estiver ausente ou for inválido, a API retornará 401.
Pontos de extremidade da API
URL base: https://app.cowlendar.com
Listar serviços
GET /public-api/v1/services
Retorna todos os serviços não arquivados na sua loja. Use este endpoint para descobrir seus IDs de serviço, tipos, durações, membros da equipe, equipamentos e categorias de participantes antes de criar reservas por meio da API.
Parâmetros de consulta:
limit (opcional, 1-100): número de resultados por página.
cursor (opcional): cursor de paginação de uma resposta anterior.
is_active (opcional, verdadeiro ou falso): filtra por serviços ativos ou inativos.
type (opcional): filtrar por tipo de serviço. Valores: classic-checkout, classic-no-checkout, multiday-checkout, multiday-no-checkout, multislot-checkout, multislot-no-checkout, fullday-checkout, fullday-no-checkout.
q (opcional, máximo 120 caracteres): busca serviços por título.
sort (opcional): -id (padrão, o mais novo primeiro), id (o mais antigo primeiro), title (A a Z), -title (Z a A).
Exemplo de solicitação:
curl "https://app.cowlendar.com/public-api/v1/services?is_active=true&limit=10" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Exemplo de resposta (abreviada):
{
"data": [
{
"id": "6789abcdef0123456789abcd",
"title": "Haircut",
"type": "classic-checkout",
"is_active": true,
"timezone": "Europe/Paris",
"durations": [30, 45, 60],
"default_duration": 30,
"avs_type": "teammates",
"meeting_location": "in_person",
"enable_participants": false,
"participants": [],
"equipments": [],
"teammates": [
{
"id": "aaa111bbb222ccc333ddd444",
"firstname": "Marie",
"lastname": "Dupont",
"email": "[email protected]",
"thumbnail": null
}
],
"product": {
"handle": "haircut",
"title": "Haircut",
"image": "https://cdn.shopify.com/..."
},
"created_at": "2025-09-01T10:00:00.000Z",
"updated_at": "2026-05-10T08:30:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
}
Compreendendo os principais campos de serviço
avs_type informa como este serviço gerencia a disponibilidade e determina se você precisa passar um teammate_id ao criar reservas:
avs_type: "teammates" significa que cada membro da equipe tem sua própria programação independente. Ao criar uma reserva, passe um teammate_id do array teammates. Se você omitir, o dono da loja será usado como substituto.
avs_type: "service" significa que o próprio serviço possui sua disponibilidade. O membro da equipe atribuído é determinado automaticamente a partir do horário da reserva. Você ainda pode passar um teammate_id para forçar a atribuição de uma pessoa específica.
avs_type: "equipment" significa que a disponibilidade é por equipamento. Semelhante ao modo de serviço, mas vinculado à capacidade do equipamento.
enable_participants informa se o serviço usa categorias de participantes digitadas como "Adulto", "Criança" ou "Sênior". Quando true, a matriz participants contém as categorias com seus IDs, quantidades mínimas e máximas e padrões. Você precisa desses IDs ao criar reservas com detalhes detalhados dos participantes. equipamentos contém recursos reserváveis, como salas, quadras, veículos ou bicicletas com suas variantes e SKUs. Para equipamentos com tracking_type: "same", todas as unidades são intercambiáveis e você pode usar qualquer SKU, normalmente base. Para tracking_type: "unique", cada unidade tem seu próprio SKU e você deve escolher entre variants[].sku. type informa o modelo de reserva. O sufixo -checkout ou -no-checkout indica se o serviço utiliza checkout Shopify. O prefixo indica o modelo de agendamento: classic para um único intervalo de tempo, multiday para vários dias, como estadias em hotéis, multislot para vários intervalos consecutivos e fullday para uma reserva de um dia inteiro.
Listar reservas
GET /public-api/v1/bookings
Retorna reservas para sua loja. Este é o endpoint que você usa para sincronizações de CRM, exportações de relatórios e painéis de reserva personalizados.
Parâmetros de consulta:
limit (opcional, 1-100): resultados por página.
cursor (opcional): cursor de paginação.
start (opcional, ISO 8601 datetime): somente reservas iniciadas a partir desta data.
end (opcional, ISO 8601 datetime): somente reservas iniciadas antes desta data.
service_id (opcional, array de IDs): filtra por um ou mais serviços.
subscription_id (opcional, matriz de IDs): filtra por assinaturas.
status (opcional, matriz): confirmed, pending, declined, canceled.
attendance (opcional, matriz): booked, pending, arrived, started, completed, no-show, delayed, paid.
customer_email (opcional): filtre pelo e-mail exato do cliente.
sort (opcional): -id (padrão, o mais novo primeiro), id (o mais antigo primeiro), start_date (o mais antigo primeiro), -start_date (o mais recente primeiro).
Exemplo: obtenha todas as reservas confirmadas para a próxima semana:
curl "https://app.cowlendar.com/public-api/v1/bookings?status=confirmed&start=2026-05-25T00:00:00Z&end=2026-06-01T00:00:00Z&sort=start_date" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Exemplo: encontre todas as reservas de um cliente específico:
curl "https://app.cowlendar.com/public-api/v1/[email protected]" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Exemplo: exportar todos os não comparecimentos de um serviço específico:
curl "https://app.cowlendar.com/public-api/v1/bookings?attendance=no-show&service_id=6789abcdef0123456789abcd" -H "Authorization: Bearer YOUR_TOKEN_HERE"
Exemplo de resposta (abreviada):
{
"data": [
{
"id": "abc123def456ghi789jkl012",
"booking_str": "COW-1234",
"service": {
"id": "6789abcdef0123456789abcd",
"title": "Haircut",
"type": "classic-checkout"
},
"start_date": "2026-05-27T14:00:00.000Z",
"end_date": "2026-05-27T14:30:00.000Z",
"timezone": "Europe/Paris",
"customer": {
"name": "Jane Smith",
"email": "[email protected]",
"phone": "+33612345678",
"locale": "en"
},
"form_data": {
"Special requests": "Window seat"
},
"quantity": 1,
"unit_quantity": 1,
"quantity_details": [],
"price": { "amount": 35, "currency": "EUR" },
"confirmation_status": "confirmed",
"attendance": "booked",
"financial_status": "paid",
"is_canceled": false,
"teammates": [
{
"id": "aaa111bbb222ccc333ddd444",
"firstname": "Marie",
"lastname": "Dupont"
}
],
"order_id": "gid://shopify/Order/123456789",
"subscription_id": null,
"created_at": "2026-05-20T09:15:00.000Z",
"updated_at": "2026-05-20T09:15:00.000Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
}
Compreender os principais campos de reserva
quantidade e unit_quantity trabalham juntos. Para um serviço clássico como corte de cabelo, quantity é o número de clientes atendidos e unit_quantity é 1. Para um serviço de vários dias, como uma estadia em hotel, quantity é o número de quartos e unit_quantity é o número de noites. Para um serviço multislot, quantity é sempre 1 e unit_quantity é o número de slots reservados. O total de unidades faturáveis é sempre quantity x unit_quantity. quantity_details é o detalhamento detalhado. Pode conter três tipos de entradas: default para uma contagem anônima, participant para um participante digitado como "Adulto" ou "Criança" ou equipment para um recurso reservável como "Tribunal 3". As reservas legadas podem ter uma matriz vazia aqui. confirmation_status é confirmed, pending ou declined. Alguns serviços exigem confirmação manual do administrador antes que uma reserva se torne ativa. atendimento rastreia o ciclo de vida de atendimento do cliente: booked, pending, arrived, started, completed, no-show, delayed, paid. order_id é o ID do pedido Shopify quando a reserva passou pela finalização da compra Shopify. É null para reservas manuais e reservas criadas por API. subscription_id vincula a reserva a um plano de assinatura, por exemplo, um passe de 10 aulas.
Crie uma reserva via API
POST /public-api/v1/bookings
Cria uma reserva programaticamente. Isso é tratado como uma reserva manual, o que significa que nenhum pedido Shopify é criado. Porém, todas as automações usuais ainda disparam normalmente: emails de confirmação para o cliente, emails de notificação e SMS para a equipe e sincronização de calendário com Google Calendar ou Outlook.
Este endpoint é o que você usa ao criar um frontend de reserva personalizado, um app móvel, um quiosque ou ao importar reservas de outro sistema.
Campos obrigatórios:
service_id (string): o ID do serviço, que você obtém em List Services.
start_date (data e hora ISO 8601): quando a reserva começa.
end_date (data e hora ISO 8601): quando a reserva termina.
timezone (string): fuso horário IANA, por exemplo Europe/Paris, America/New_York, Asia/Tokyo.
customer (objeto): deve conter pelo menos email ou phone. Também pode incluir name, firstname, lastname e locale, que é o código do idioma para os e-mails de notificação do cliente.
Campos opcionais:
quantity (número inteiro, padrão 1): unidades de nível superior reservadas. Ignorado se quantity_details for fornecido.
unit_quantity (inteiro, padrão 1): subunidades por unidade de nível superior.
quantity_details (matriz): detalhamento por tipo de participante ou equipamento. Quando fornecido, quantity é calculado automaticamente como a soma.
teammate_id (string): atribuição forçada de um membro específico da equipe.
subscription_id (string): vincule esta reserva a uma assinatura existente e deduza um crédito.
form_data (objeto): chave personalizada ou pares de valores do seu formulário de reserva.
price (número, padrão 0): preço total líquido da reserva.
currency (string): código ISO 4217 como USD, EUR ou GBP. O padrão é a moeda da sua loja.
Exemplo: crie uma reserva simples
Um cliente reserva um corte de cabelo de 30 minutos com um estilista específico:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-01T10:00:00.000Z",
"end_date": "2026-06-01T10:30:00.000Z",
"timezone": "Europe/Paris",
"customer": {
"email": "[email protected]",
"name": "John Doe",
"phone": "+33612345678",
"locale": "en"
},
"teammate_id": "aaa111bbb222ccc333ddd444",
"quantity": 1,
"price": 35,
"currency": "EUR"
}'
Cowlendar cria a reserva e aciona o e-mail de confirmação, SMS e convite de calendário para o cliente e para o membro da equipe.
Exemplo: reserva de grupo com participantes digitados
Uma família reserva uma visita guiada: 3 adultos e 2 crianças com preços diferentes:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-15T09:00:00.000Z",
"end_date": "2026-06-15T11:00:00.000Z",
"timezone": "America/New_York",
"customer": {
"email": "[email protected]",
"firstname": "Sarah",
"lastname": "Miller",
"phone": "+15551234567",
"locale": "en"
},
"quantity_details": [
{
"type": "participant",
"name": "Adult",
"quantity": 3,
"participant_id": "adult_id_from_service"
},
{
"type": "participant",
"name": "Child (under 12)",
"quantity": 2,
"participant_id": "child_id_from_service"
}
],
"price": 175,
"currency": "USD"
}'
Os valores participant_id vêm da matriz participants[] do serviço na resposta List Services. O total de quantity é automaticamente definido como 5 (3 + 2).
Exemplo: reserva com equipamento
Um cliente reserva uma quadra de tênis por 1 hora:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-10T16:00:00.000Z",
"end_date": "2026-06-10T17:00:00.000Z",
"timezone": "Europe/London",
"customer": {
"email": "[email protected]",
"name": "Tom Wilson"
},
"quantity_details": [
{
"type": "equipment",
"name": "Court 3",
"quantity": 1,
"equipment_id": "eee555fff666ggg777hhh888",
"equipment_sku": "COURT3",
"capacity": 4
}
],
"price": 25,
"currency": "GBP"
}'
O equipment_id e o equipment_sku vêm do array equipments[] do serviço. Para equipamentos com tracking_type: "same", utilize qualquer SKU, normalmente base. Para tracking_type: "unique", use o variants[].sku específico para a unidade desejada.
Exemplo: reserva vinculada a assinatura
Um membro do estúdio de ioga usa um crédito do seu passe mensal de 10 aulas:
curl -X POST https://app.cowlendar.com/public-api/v1/bookings -H "Authorization: Bearer YOUR_TOKEN_HERE" -H "Content-Type: application/json" -d '{
"service_id": "6789abcdef0123456789abcd",
"start_date": "2026-06-05T08:00:00.000Z",
"end_date": "2026-06-05T09:00:00.000Z",
"timezone": "America/Los_Angeles",
"customer": {
"email": "[email protected]",
"name": "Lisa Chen",
"locale": "en"
},
"subscription_id": "fff000eee111ddd222ccc333",
"price": 0
}'
A assinatura deve ser active, pertencer ao mesmo serviço e ter remaining_credits > 0. Cowlendar diminui automaticamente a contagem de crédito e envia o e-mail de atualização da assinatura ao cliente.
Códigos de erro
Erro de validação 400. Um campo obrigatório está faltando ou está no formato errado. O corpo do erro informa qual campo.
401 Token ausente ou inválido.
403 A loja está inativa ou o acesso é restrito.
404 Serviço não encontrado.
422 Reserva rejeitada. O horário já está ocupado, o membro da equipe está indisponível, o equipamento está lotado ou a assinatura não tem créditos restantes. Verifique a mensagem de erro pelo motivo específico.
429 Limite de taxa excedido. Afaste suas solicitações e tente novamente depois de um momento.
Paginação
Todos os pontos de extremidade da lista usam paginação baseada em cursor. Quando a resposta incluir "has_more": true, passe o valor next_cursor como parâmetro cursor em sua próxima solicitação:
curl "https://app.cowlendar.com/public-api/v1/bookings?cursor=eyJpZCI6IjY3ODlhYmNkIn0=&limit=50" -H "Authorization: Bearer YOUR_TOKEN_HERE"
A ordem de classificação é preservada nas páginas. O tamanho máximo da página é de 100 resultados.
Webhooks: notificações de reserva em tempo real
Embora a API permita extrair dados sob demanda, os webhooks enviam dados para você instantaneamente. Cada vez que uma reserva é criada, confirmada, cancelada ou reprogramada, Cowlendar envia um HTTP POST assinado para o URL que você configurou.
Webhooks são essenciais para construir integrações em tempo real: sincronizações de CRM, notificações do Slack, sequências automatizadas de e-mail, painéis ao vivo ou qualquer fluxo de trabalho que precise reagir a eventos de reserva conforme eles acontecem.
Configurar um endpoint de webhook
Passo 1: No administrador do Shopify, abra o aplicativo Cowlendar.
Passo 2: Vá para Settings > Webhooks.
Passo 3: Clique em Add endpoint.
Passo 4: Insira um nome para seu endpoint, por exemplo “HubSpot CRM”, “Zapier” ou “Analytics pipeline”.
Passo 5: Cole seu URL HTTPS. A URL deve usar HTTPS. Se você estiver usando Zapier ou Make, cole o URL do webhook fornecido.
Passo 6: Verifique os eventos que deseja receber. Você pode escolher qualquer combinação.
Passo 7: Clique em Create.
Cowlendar exibe um segredo de assinatura começando com whsec_. Copie-o imediatamente e guarde-o com segurança. Este segredo é mostrado apenas uma vez e você precisa dele para verificar as solicitações de webhook recebidas em seu servidor.
Você pode editar qualquer endpoint posteriormente para alterar o URL ou ativar ou desativar eventos específicos:
Os 8 eventos de webhook
booking.created é acionado quando uma nova reserva é criada a partir de qualquer fonte: o widget da vitrine, o painel de administração, o PDV ou a API pública. booking.confirmed é acionado quando uma reserva pendente é confirmada manualmente pelo administrador. booking.declined é acionado quando uma reserva pendente é recusada. booking.canceled é acionado quando uma reserva é cancelada pelo cliente ou administrador. booking.rescheduled é acionado quando a data de uma reserva ou os membros da equipe atribuídos são alterados. booking.attendance_changed é acionado quando o status de atendimento muda, por exemplo, de "reservado" para "concluído" ou quando é marcado como "não comparecimento". subscription.created é acionado quando uma nova assinatura recorrente é criada. subscription.billing_failed é acionado quando uma tentativa de cobrança de assinatura falha. Após 3 falhas consecutivas, o contrato de assinatura é automaticamente cancelado em Shopify e o status local muda para canceled.
Qual é a aparência de uma carga útil de webhook
Cada entrega de webhook é um HTTP POST com corpo JSON. Aqui está um exemplo completo de booking.created:
{
"id": "evt_2b86f80a-1c3d-4e5f-9a8b-7c6d5e4f3a2b",
"type": "booking.created",
"created_at": "2026-05-20T09:15:00.000Z",
"shop_domain": "yourshop.myshopify.com",
"data": {
"id": "abc123def456ghi789jkl012",
"booking_str": "COW-1234",
"service": {
"id": "6789abcdef0123456789abcd",
"title": "Haircut",
"type": "classic-checkout"
},
"start_date": "2026-05-27T14:00:00.000Z",
"end_date": "2026-05-27T14:30:00.000Z",
"timezone": "Europe/Paris",
"customer": {
"name": "Jane Smith",
"email": "[email protected]",
"phone": "+33612345678",
"locale": "en"
},
"form_data": {},
"quantity": 1,
"unit_quantity": 1,
"quantity_details": [],
"price": { "amount": 35, "currency": "EUR" },
"confirmation_status": "confirmed",
"attendance": "booked",
"financial_status": null,
"is_canceled": false,
"teammates": [
{
"id": "aaa111bbb222ccc333ddd444",
"firstname": "Marie",
"lastname": "Dupont"
}
],
"order_id": null,
"subscription_id": null,
"created_at": "2026-05-20T09:15:00.000Z",
"updated_at": "2026-05-20T09:15:00.000Z"
}
}
O campo data contém o objeto completo de reserva ou assinatura exatamente na mesma estrutura da resposta da API.
Para eventos subscription.billing_failed, a carga inclui um objeto previous adicional com o failure_count antes da tentativa atual, para que você possa acompanhar o escalonamento, por exemplo, enviando um e-mail urgente após a segunda falha.
Solicite cabeçalhos em cada entrega de webhook
X-Cowlendar-Event-Id é o ID exclusivo do evento. Cowlendar reutiliza o mesmo ID ao tentar novamente uma entrega com falha. Use-o para desduplicar do seu lado.
X-Cowlendar-Event-Type é a sequência do tipo de evento, por exemplo booking.created.
X-Cowlendar-Timestamp é o carimbo de data/hora Unix em segundos quando Cowlendar assinou a solicitação.
X-Cowlendar-Signature é a assinatura HMAC-SHA256 no formato t=<timestamp>,v1=<hex>.
Verifique a assinatura do webhook
Cada solicitação de webhook é assinada para que você possa confirmar se realmente veio do Cowlendar e não foi adulterado. A assinatura é calculada como HMAC-SHA256(secret, timestamp + "." + raw_body) usando o segredo de assinatura do seu endpoint.
Você também deve rejeitar solicitações com carimbo de data/hora superior a 5 minutos para evitar ataques de repetição.
Node.js (Expresso):
import crypto from "crypto";
import express from "express";
const app = express();
app.post(
"/webhooks/cowlendar",
express.raw({ type: "application/json" }),
(req, res) => {
const sigHeader =
req.header("X-Cowlendar-Signature") || "";
const timestamp =
req.header("X-Cowlendar-Timestamp") || "";
const rawBody = req.body.toString("utf8");
// Reject requests older than 5 minutes
if (
Math.abs(Date.now() / 1000 - parseInt(timestamp, 10))
> 300
) {
return res.status(400).send("Timestamp too old");
}
const expected = crypto
.createHmac(
"sha256",
process.env.COWLENDAR_WEBHOOK_SECRET
)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const received = sigHeader
.split(",")
.find((p) => p.startsWith("v1="))
?.slice(3);
if (
!received ||
!crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
)
) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody);
// Short-circuit test pings
if (event.type === "webhook.ping") {
return res.status(200).send("pong");
}
// Handle real events here
res.status(200).send("ok");
}
);
Python (frasco):
import hmac, hashlib, os, time, json
from flask import Flask, request
app = Flask(__name__)
@app.post("/webhooks/cowlendar")
def cowlendar_webhook():
sig_header = request.headers.get(
"X-Cowlendar-Signature", ""
)
timestamp = request.headers.get(
"X-Cowlendar-Timestamp", ""
)
raw_body = request.get_data(as_text=True)
# Reject requests older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return "Timestamp too old", 400
expected = hmac.new(
os.environ["COWLENDAR_WEBHOOK_SECRET"].encode(),
f"{timestamp}.{raw_body}".encode(),
hashlib.sha256,
).hexdigest()
received = next(
(p[3:] for p in sig_header.split(",")
if p.startswith("v1=")),
None,
)
if not received or not hmac.compare_digest(
expected, received
):
return "Invalid signature", 401
event = json.loads(raw_body)
if event["type"] == "webhook.ping":
return "pong", 200
# Handle real events here
return "ok", 200
PHP:
<?php
$secret = getenv("COWLENDAR_WEBHOOK_SECRET");
$rawBody = file_get_contents("php://input");
$sigHeader = $_SERVER["HTTP_X_COWLENDAR_SIGNATURE"] ?? "";
$timestamp = $_SERVER["HTTP_X_COWLENDAR_TIMESTAMP"] ?? "";
// Reject requests older than 5 minutes
if (abs(time() - (int)$timestamp) > 300) {
http_response_code(400);
exit("Timestamp too old");
}
$expected = hash_hmac(
"sha256",
$timestamp . "." . $rawBody,
$secret
);
$received = null;
foreach (explode(",", $sigHeader) as $part) {
if (str_starts_with($part, "v1=")) {
$received = substr($part, 3);
break;
}
}
if (!$received || !hash_equals($expected, $received)) {
http_response_code(401);
exit("Invalid signature");
}
$event = json_decode($rawBody, true);
if ($event["type"] === "webhook.ping") {
http_response_code(200);
exit("pong");
}
// Handle real events here
http_response_code(200);
echo "ok";
Teste seu webhook antes de ir ao ar
Cada endpoint de webhook possui um botão Test no admin. Clicar nele envia um evento webhook.ping sintético para o seu URL. Este evento usa a mesma assinatura, cabeçalhos e comportamento de nova tentativa que eventos reais, mas com uma carga de teste inofensiva:
{
"id": "evt_2b86f80a...",
"type": "webhook.ping",
"created_at": "2026-05-13T14:57:57.704Z",
"shop_domain": "yourshop.myshopify.com",
"data": {
"message": "This is a test event from Cowlendar. If you can read this, your endpoint is reachable."
}
}
Use-o para confirmar se Cowlendar pode acessar seu URL, se as configurações de DNS, HTTPS e firewall estão funcionando e para validar seu código de verificação de assinatura HMAC sem criar uma reserva real. Uma reserva real enviaria e-mails a um cliente, criaria eventos de calendário e potencialmente acionaria um pedido Shopify.
Após clicar em Testar, marque o botão See deliveries para ver o código de status HTTP que seu endpoint retornou. Um 200 verde significa que tudo está funcionando.
Tentar novamente o comportamento e desativar automaticamente
Se o seu endpoint retornar um erro 5xx, um 429 ou um tempo limite de rede, Cowlendar tentará novamente nesta programação:
Após 1 minuto, após 5 minutos, após 25 minutos, após 2 horas, após 10 horas, após 50 horas. São 6 tentativas no total, abrangendo aproximadamente 3 dias.
Após aproximadamente 20 falhas consecutivas em todos os eventos, o endpoint é automaticamente desativado e Cowlendar envia um e-mail ao proprietário da loja. Você pode reativar o endpoint de Settings > Webhooks quando seu servidor estiver online novamente.
O mesmo X-Cowlendar-Event-Id é reutilizado em novas tentativas. Sempre armazene IDs de eventos processados e ignore duplicatas.
Qualquer resposta 2xx conta como sucesso. Você não precisa retornar um corpo ou tipo de conteúdo específico.
Práticas recomendadas de webhook
Responda em 10 segundos. Se o seu manipulador precisar realizar um trabalho pesado, como chamar outra API, gravar em um banco de dados ou executar uma operação complexa, faça-o de forma assíncrona. Aceite o webhook imediatamente com um 200 e processe-o em uma tarefa ou fila em segundo plano. Desduplicar usando o ID do evento. Armazene cada X-Cowlendar-Event-Id que você processar. Se você receber o mesmo ID novamente, pule-o. Cowlendar tenta novamente com o mesmo ID em caso de falha. Use comparação de tempo constante para assinaturas. crypto.timingSafeEqual em Node.js, hmac.compare_digest em Python e hash_equals em PHP. A comparação regular de strings como === é vulnerável a ataques de temporização. Não tente novamente. Cowlendar lida com novas tentativas automaticamente. Se o seu servidor teve um problema temporário, retorne 5xx e Cowlendar retornará em sua programação de novas tentativas. Trate webhook.ping explicitamente. Retorne 200 imediatamente para eventos de ping. Isso permite que você use o botão Testar sem acionar sua lógica de negócios.
Limitações atuais
As reservas criadas via API são reservas manuais. Elas não criam um pedido Shopify. Se o seu serviço normalmente usa checkout Shopify, a API o ignora. E-mails de confirmação, SMS e sincronização de calendário ainda funcionam normalmente. Ainda não há endpoint de disponibilidade. A API atual permite ler serviços e reservas, além de criar reservas. Não expõe os horários disponíveis. Para verificar se um intervalo de tempo está disponível, tente criar a reserva e tratar a resposta 422 se o intervalo estiver ocupado. Um endpoint de disponibilidade pode ser adicionado em uma atualização futura. Ainda não há atualização ou exclusão de endpoints. Você pode criar e ler reservas, mas não pode atualizá-las, reagendá-las ou cancelá-las via API. Gerencie essas ações por meio do administrador Cowlendar por enquanto. Aplicam-se limites de taxa. Se você enviar muitas solicitações em uma janela curta, a API retornará 429. Para operações em massa, como exportar todas as reservas, adicione um pequeno atraso entre as solicitações paginadas, por exemplo, 200 a 500 ms. Os segredos do Webhook são mostrados apenas uma vez. Se você perder seu segredo de assinatura, exclua o endpoint e crie um novo para obter um novo segredo. Webhooks requerem HTTPS. Endpoints HTTP não são aceitos.
Perguntas frequentes
Posso usar a API Cowlendar com Zapier ou Make sem escrever código?
Sim. Para webhooks, recebendo eventos de Cowlendar, crie um gatilho "Custom Webhook" em Zapier ou Make, copie o URL fornecido e adicione-o como um endpoint em Cowlendar Settings > Webhooks. Cada evento de reserva aciona sua automação. Para a API, enviando solicitações para Cowlendar, use a ação "HTTP Request" no Zapier ou Make para chamar qualquer endpoint da API Cowlendar com seu token Bearer.
As reservas criadas por meio da API enviam e-mails aos clientes?
Sim. As reservas criadas pela API são tratadas exatamente como as reservas manuais. E-mails de confirmação, notificações por SMS e sincronização de calendário são acionados automaticamente, assim como fariam para uma reserva feita através do painel de administração.
O que acontece se meu endpoint do webhook falhar?
Cowlendar tenta novamente entregas com falha 6 vezes durante aproximadamente 3 dias. Após cerca de 20 falhas consecutivas, o endpoint é desativado automaticamente e você recebe um email. Reative-o em Settings > Webhooks quando o servidor estiver de volta.
Posso ter vários endpoints de webhook?
Sim. Crie quantos você precisar, cada um com diferentes seleções de eventos e URLs. Por exemplo, um endpoint para seu CRM ouvindo booking.created e booking.canceled, e outro para seu analytics pipeline ouvindo todos os eventos.
Posso revogar um token de API? Sim. Vá para Settings > Public API e clique no botão Revoke . O token para de funcionar imediatamente. Existe um ambiente sandbox ou de teste?
Atualmente não. Use o botão de teste webhook.ping para validar a configuração do seu webhook sem criar reservas reais. Para testes de API, recomendamos a criação de um serviço de teste em seu administrador Cowlendar que você usa apenas para desenvolvimento.
Onde está a referência completa da API?
A documentação interativa da API com todos os esquemas, parâmetros e exemplos de resposta está em https://app.cowlendar.com/public-api/docs