Apresente seu projeto
Vincule sua Steam, abra /loot no Discord e ative sua participação. No seu painel privado, envie a proposta em Parcerias.
Comunidade conectada, com permissão
Esta API recebe eventos de atividade de projetos aprovados pela equipe. Eventos elegíveis concedem somente XP interno. Não emitem créditos, não abrem caixas e não compram ou entregam skins.
Vincule sua Steam, abra /loot no Discord e ative sua participação. No seu painel privado, envie a proposta em Parcerias.
A equipe avalia a proposta e a forma de comprovar atividade. O envio do formulário não concede acesso; a aprovação ocorre manualmente.
Após a aprovação, o responsável autorizado recebe um ID e uma chave por canal privado. Guarde a chave no servidor; nunca no navegador, repositório público ou URL.
Xplay, Dropskin e outros serviços externos não estão conectados automaticamente. Uma candidatura aprovada não implica parceria com essas plataformas nem autorização para coletar dados delas.
01 / Contrato de eventos
Envie JSON UTF-8 com Content-Type: application/json. O corpo bruto pode ter até 8.192 bytes e deve conter somente os seis campos abaixo.
{
"eventId": "evento-unico-do-parceiro",
"discordId": "ID_DISCORD_DO_MEMBRO",
"steamId": "STEAMID64_DO_MESMO_MEMBRO",
"kind": "activity",
"seq": 1,
"expiresAt": 0
}Os marcadores de identidade e expiresAt: 0 acima são ilustrativos. Substitua por identidades autorizadas e uma expiração futura; não envie o exemplo literalmente.
| Campo | Regra |
|---|---|
eventId | Identificador estável e único por parceiro, de 1 a 160 caracteres: letras, números, ponto, sublinhado, dois-pontos ou hífen. |
discordId | String numérica de 17 a 20 dígitos do membro participante. |
steamId | String de 17 dígitos; deve corresponder à Steam atualmente vinculada àquele membro. |
kind | Exatamente activity. Nenhum campo amount é aceito. |
seq | Inteiro positivo seguro, estritamente crescente por parceiro. Persista o contador entre reinícios e coordene emissores concorrentes. |
expiresAt | Unix em segundos, futuro no processamento e no máximo timestamp + 300. |
02 / Autenticação
A assinatura usa HMAC-SHA256 em hexadecimal minúsculo. A chave do HMAC é o SHA256 da chave secreta aprovada, em bytes; não é a representação textual desse hash.
| Cabeçalho | Valor |
|---|---|
x-zas-key | ID do parceiro aprovado. |
x-zas-timestamp | Unix em segundos, como string de dez dígitos. Janela: até 300 segundos atrás ou 30 segundos à frente. |
x-zas-nonce | Valor aleatório base64url, de 16 a 128 caracteres, único para cada evento novo. |
x-zas-signature | HMAC-SHA256 com 64 caracteres hexadecimais minúsculos. |
signingKey = SHA256(secret) em bytes
message = keyId + '\n' + timestamp + '\n' + nonce + '\n' + rawBody
signature = HMAC-SHA256(signingKey, message) em hexadecimalUse quebras de linha LF entre as partes. Serialize o JSON uma vez: espaços, ordem de campos e quebras de linha fazem parte do corpo assinado. Não envie a chave secreta em um cabeçalho.
03 / Referência Node.js
Exemplo para backend com Node.js e fetch nativo. Configure ZAS_BASE_URL com a origem HTTPS da comunidade e as duas credenciais recebidas privadamente. Nenhum segredo real está neste guia.
import { createHash, createHmac, randomBytes } from 'node:crypto';
// Configure estas variáveis somente no backend do parceiro.
const baseUrl = new URL(process.env.ZAS_BASE_URL);
const keyId = process.env.ZAS_PARTNER_ID;
const secret = process.env.ZAS_PARTNER_SECRET;
if (baseUrl.protocol !== 'https:' || baseUrl.username || baseUrl.password
|| !keyId || !secret) throw new Error('Configure o acesso aprovado.');
export function prepareEvent({ eventId, discordId, steamId, seq }) {
// seq deve vir de um contador persistente e atômico por parceiro.
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = randomBytes(24).toString('base64url');
const rawBody = JSON.stringify({
eventId, discordId, steamId, kind: 'activity', seq,
expiresAt: Number(timestamp) + 120,
});
const signingKey = createHash('sha256').update(secret, 'utf8').digest();
const message = [keyId, timestamp, nonce, rawBody].join('\n');
const signature = createHmac('sha256', signingKey)
.update(message, 'utf8').digest('hex');
return {
rawBody,
headers: {
'Content-Type': 'application/json',
'x-zas-key': keyId,
'x-zas-timestamp': timestamp,
'x-zas-nonce': nonce,
'x-zas-signature': signature,
},
};
}
export async function sendSavedEnvelope(envelope) {
// Salve o envelope antes do envio; retries reutilizam os mesmos bytes.
const response = await fetch(new URL('/api/partners/v1/events', baseUrl), {
method: 'POST', redirect: 'error',
headers: envelope.headers, body: envelope.rawBody,
signal: AbortSignal.timeout(10_000),
});
const data = await response.json();
if (!response.ok || data.ok !== true) {
throw new Error('Evento não confirmado. HTTP ' + response.status);
}
return data.result;
}Sua aplicação deve gerar o eventId para uma atividade comprovada, reservar a sequência em transação e salvar o envelope antes de enviar. O exemplo não implementa armazenamento nem comprovação da atividade.
04 / Entrega e limites
Dentro da janela válida, repita o envelope salvo após uma falha de rede. O mesmo evento e corpo devolvem o resultado anterior, sem repetir a recompensa de XP. Alterar o corpo mantendo o ID falha.
Se a janela expirou, confira a entrega com a equipe antes de emitir outro evento. Não reinicie a sequência nem gere novos IDs apenas para contornar uma resposta incerta.
Cada evento elegível concede 5 XP, até dez eventos por dia por membro e no máximo um por minuto. Não gera créditos nem tempo de voz para o check-in.
O receptor limita a 60 requisições válidas por minuto por parceiro e 12 por minuto por membro. Uma resposta aceita não promete pontos além desses limites.
O destinatário precisa ser membro, ter Steam compatível e participação ativa. O titular da parceria também precisa manter seu vínculo e participação.
Chaves podem ser revogadas pela equipe. Sincronize o relógio do backend e proteja credenciais, envelopes e registros de identidade com acesso restrito.
Sucesso responde { "ok": true, "result": … }; falha responde { "ok": false, "message": … } com status HTTP correspondente. Leia o resultado, trate limites e não registre chaves ou assinaturas em logs públicos.