Serviços externos
Entregue um benefício por um sistema seu, que recebe o pedido de entrega por webhook e confirma pela API.
Um serviço externo é um sistema seu (ou de terceiros) que entrega algo que a MinePag não entrega sozinha: uma licença em outro sistema, uma conta em um painel, uma cópia personalizada de um arquivo. A MinePag pede a entrega com o evento delivery.requested e o serviço confirma o resultado pela API.
O serviço de packs com marca d'água é um exemplo pronto de serviço externo.
Como funciona
A MinePag pede a entrega
Quando o pagamento é confirmado, a MinePag envia delivery.requested só para o serviço escolhido no benefício. O envio é assinado no padrão Standard Webhooks.
O serviço responde rápido
O serviço confere a assinatura e responde com um status 2xx em até 10 segundos. A entrega fica Em processamento no painel.
O serviço confirma pela API
Depois de entregar, o serviço chama POST /api/v1/deliveries/{operationId}/complete. Se não conseguir, chama POST /api/v1/deliveries/{operationId}/fail. A entrega passa para Entregue, Nova tentativa agendada ou Tentativas esgotadas.
Se o serviço não confirmar em 24 horas, a MinePag considera a tentativa perdida e envia um novo delivery.requested para a mesma entrega, até 6 tentativas.
Configurar
Crie uma chave secreta
Em Desenvolvedores › Chaves de API, crie uma chave secreta (sk_…) com o escopo deliveries:write. O serviço usa essa chave para confirmar as entregas. Guarde a chave só no servidor do serviço. Veja Autenticação.
Cadastre o serviço
Em Entregas › Serviços externos, clique em Adicionar serviço. O formulário já vem com o tipo Serviço externo de entrega e os eventos delivery.requested, entitlement.revoked e order.refunded marcados.
- URL: endereço HTTPS público do serviço. Endereços locais, internos ou IPs privados são recusados.
- Descrição (opcional): nome para reconhecer o serviço no painel.
Clique em Criar endpoint e copie o Segredo de assinatura (whsec_…). Você pode vê-lo de novo na página do serviço; cada visualização fica nos logs de auditoria.
Use no produto
Em Catálogo › Produtos, abra o produto, adicione o benefício Serviço externo e escolha o serviço.
Teste
Na página do serviço, use Testar envio para conferir se ele responde e se a assinatura confere. Depois, faça uma compra com o gateway em Sandbox.
O evento delivery.requested
{
"id": "0d4a3b8e-6f1c-4a7e-9b2d-5c8f1e3a7b90",
"type": "delivery.requested",
"apiVersion": "2026-10-01",
"createdAt": "2026-10-12T14:30:00.000Z",
"projectId": "7c1e9f40-2b3d-4c5a-8e6f-9a0b1c2d3e4f",
"data": {
"delivery": {
"operationId": "5f2b8c1d-3e4a-4b6c-9d7e-8f0a1b2c3d4e",
"entitlementId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"channel": "webhook",
"kind": "grant",
"attempt": 1,
"orderId": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
"orderItemId": "6e7f8a9b-0c1d-4e2f-9a3b-4c5d6e7f8a9b",
"productId": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
"benefitId": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
"quantity": 1
},
"customer": {
"id": "4d5e6f7a-8b9c-4d0e-8f1a-2b3c4d5e6f7a",
"email": "cliente@exemplo.com",
"name": "Ana Souza"
},
"benefit": {
"id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
"type": "external_webhook",
"config": { "endpointId": "8e9f0a1b-2c3d-4e4f-9a5b-6c7d8e9f0a1b" }
}
}
}| Campo | Para que serve |
|---|---|
delivery.operationId | Identifica a entrega. Use na URL de confirmação e para não entregar duas vezes. |
delivery.attempt | Número da tentativa. Um novo delivery.requested da mesma entrega chega com o mesmo operationId e outra tentativa. |
delivery.kind | grant (primeira entrega) ou resend (reenvio). |
delivery.entitlementId | O acesso liberado do cliente. É o que chega em entitlement.revoked quando o acesso termina. |
delivery.productId e delivery.quantity | O que foi comprado e quantas unidades. |
customer | Quem comprou. |
Os cabeçalhos webhook-id, webhook-timestamp e webhook-signature acompanham cada envio. Como verificar a assinatura em cada linguagem está em Webhooks.
Responda antes de processar
A MinePag espera a resposta por até 10 segundos. Responda 2xx assim que verificar a assinatura e processe em segundo plano. Se o serviço responder com erro ou não responder, o mesmo evento é reenviado depois de 1 minuto, 5 minutos, 30 minutos, 2 horas, 6 horas e 24 horas.
Confirmar a entrega
POST /api/v1/deliveries/{operationId}/complete
Authorization: Bearer sk_live_…
Content-Type: application/json
{
"externalReference": "licenca_123",
"downloadUrl": "https://servico.sualoja.com.br/p/licenca_123",
"expiresAt": "2026-12-31T23:59:59Z",
"metadata": { "plano": "anual" }
}| Campo | Regra |
|---|---|
externalReference | Obrigatório. Identificador da entrega no seu sistema, até 255 caracteres. Aparece no detalhe da entrega no painel. |
downloadUrl | Opcional. Link HTTPS de até 2.048 caracteres. |
expiresAt | Opcional. Data ISO 8601 com fuso em que o link deixa de valer. |
watermarked | Opcional. true quando o link entrega cópias com marca d'água por comprador. Veja o aviso de faturamento abaixo. |
metadata | Opcional. Até 20 chaves com textos (até 500 caracteres), números ou booleanos. |
A resposta traz o estado final:
{ "data": { "operationId": "5f2b8c1d-3e4a-4b6c-9d7e-8f0a1b2c3d4e", "status": "succeeded" } }Confirmar de novo uma entrega já concluída devolve a mesma resposta, sem duplicar nada. Confirmar uma entrega cancelada ou com tentativas esgotadas devolve 409 com o código delivery_already_finalized.
Quando a confirmação conta como entrega com marca d'água
Só a primeira entrega (kind: grant) confirmada com downloadUrl e watermarked: true conta uma unidade de "entregas com marca d'água" na sua fatura (pedidos em Sandbox não contam). Um link comum, sem marca d'água, não conta, e reenvios também não. Veja Preços.
Reenviar uma entrega
Para pedir de novo a entrega ao serviço (por exemplo, depois de trocar a chave que assina os links), use Reenviar ao serviço externo no detalhe da entrega, no painel, ou chame:
POST /api/v1/deliveries/{operationId}/resend
Authorization: Bearer sk_live_…
Idempotency-Key: 6f1c2a9e-…A chave precisa do escopo deliveries:write. A MinePag cria uma entrega nova com kind: resend e envia um novo delivery.requested ao serviço. Só acessos ativos podem ser reenviados.
Informar uma falha
POST /api/v1/deliveries/{operationId}/fail
Authorization: Bearer sk_live_…
Content-Type: application/json
{
"code": "product_not_configured",
"message": "Produto sem arquivo vinculado.",
"retryable": true
}| Campo | Regra |
|---|---|
code | Obrigatório. Letras minúsculas, números e _, até 64 caracteres. |
message | Obrigatório. Até 500 caracteres. Aparece para a loja no detalhe da entrega. |
retryable | true agenda uma nova tentativa, com um novo delivery.requested. false (padrão) manda a entrega direto para Tentativas esgotadas e avisa a loja. |
externalReference | Opcional, como na confirmação. |
A resposta traz status: failed quando uma nova tentativa foi agendada e dead_letter quando as tentativas se esgotaram. Informar a falha de uma entrega já concluída ou cancelada devolve 409 com o código delivery_already_finalized.
Exemplo de serviço em Node.js
Um serviço mínimo com Express que verifica a assinatura, responde na hora e confirma pela API. Ele usa só node:crypto para a assinatura e o fetch nativo do Node 18 ou mais recente.
import { createHmac, timingSafeEqual } from 'node:crypto';
import express from 'express';
const MINEPAG_API = 'https://minepag.com.br/api/v1';
const API_KEY = process.env.MINEPAG_SECRET_KEY; // sk_… com escopo deliveries:write
const WEBHOOK_SECRETS = process.env.MINEPAG_WEBHOOK_SECRETS.split(' '); // whsec_…; dois durante uma rotação
function isSignedByMinePag(headers, body) {
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signatures = String(headers['webhook-signature'] ?? '').split(' ');
if (!id || !/^\d{1,12}$/.test(timestamp ?? '')) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
return WEBHOOK_SECRETS.some(secret => {
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = createHmac('sha256', key).update(`${id}.${timestamp}.${body}`).digest();
return signatures.some(entry => {
if (!entry.startsWith('v1,')) return false;
const received = Buffer.from(entry.slice(3), 'base64');
return received.length === expected.length && timingSafeEqual(received, expected);
});
});
}
async function callMinePag(path, payload) {
const response = await fetch(`${MINEPAG_API}${path}`, {
method: 'POST',
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (response.status === 409) return; // a entrega já tinha sido finalizada
if (!response.ok) throw new Error(`A MinePag respondeu ${response.status}`);
}
async function handleDeliveryRequested({ delivery, customer }) {
let license;
try {
// Sua lógica. Use delivery.operationId como chave para não emitir duas vezes.
license = await issueLicense({
operationId: delivery.operationId,
entitlementId: delivery.entitlementId,
productId: delivery.productId,
quantity: delivery.quantity,
email: customer.email
});
} catch {
await callMinePag(`/deliveries/${delivery.operationId}/fail`, {
code: 'license_error',
message: 'Não foi possível emitir a licença.',
retryable: true
});
return;
}
await callMinePag(`/deliveries/${delivery.operationId}/complete`, { externalReference: license.id });
}
const app = express();
app.post('/webhooks/minepag', express.raw({ type: 'application/json' }), (req, res) => {
const body = req.body.toString('utf8');
if (!isSignedByMinePag(req.headers, body)) return res.sendStatus(401);
res.sendStatus(202);
const event = JSON.parse(body);
if (event.type === 'delivery.requested') {
handleDeliveryRequested(event.data).catch(error => console.error('Falha na entrega', error));
}
if (event.type === 'entitlement.revoked') {
revokeLicense(event.data.entitlement.id).catch(error => console.error('Falha na revogação', error));
}
});
app.listen(3000);issueLicense e revokeLicense são funções do seu sistema. Em produção, coloque os eventos em uma fila em vez de processar na mesma requisição, para não perder trabalho se o processo reiniciar. Se o serviço cair antes de confirmar, a MinePag pede a entrega de novo depois de 24 horas.
Leia o corpo bruto
A assinatura é calculada sobre o corpo exatamente como foi enviado. Se o seu framework converter o JSON antes de você verificar, a assinatura não confere. No Express, use express.raw só nessa rota.
Revogação
A MinePag não envia delivery.requested para revogar. Quando o acesso do cliente termina (reembolso total, contestação, cancelamento, fim da assinatura ou revogação manual), ela envia entitlement.revoked para os endpoints inscritos nesse evento, com entitlement.id igual ao delivery.entitlementId recebido na entrega. Use esse identificador para desfazer a entrega no seu sistema.
O evento order.refunded traz fullyRefunded. Um reembolso parcial não revoga acessos.
Situações do serviço
| Situação | O que significa |
|---|---|
| Ativo | O serviço está recebendo os envios. |
| Com falhas | O serviço falhou em 3 envios seguidos. O proprietário e os administradores da loja recebem uma notificação. |
| Desativado | O serviço falhou 20 vezes seguidas por mais de 3 dias, ou alguém o desativou. As entregas para ele ficam com nova tentativa a cada hora até você reativá-lo. |
Em Entregas › Serviços externos, cada serviço mostra as entregas aguardando confirmação, as entregues em 30 dias e as com falha. Se o serviço for removido, as entregas que dependem dele vão para Tentativas esgotadas.
Acompanhar pela API
Para conferir o estado das entregas a partir do seu sistema, use Listar entregas e Consultar entrega com uma chave de escopo deliveries:read. Endpoints de webhook comuns também podem assinar delivery.completed e delivery.failed.