# Celcoin Webhooks

Celcoin (a Brazilian banking-as-a-service platform: Pix, TED, accounts, onboarding; docs are in
Portuguese) sends a JSON webhook when something happens, for example a Pix is received or an
onboarding proposal is decided. Subscriptions are created through the Celcoin API, one per
`entity`. Webhooks are **not signed**. Your endpoint authenticates the sender with **HTTP Basic
auth**: the `login` and `pwd` you set in the subscription's `auth` object come back on every
notification as `Authorization: Basic base64(login:pwd)`.

> **Hookdeck setup note:** Use Basic auth (auth.type basic) on the webhook subscription, not JWT/OAuth 2.0.

Celcoin also offers a JWT/OAuth 2.0 mode (`auth.type` `jwt`, where Celcoin first calls your token
endpoint). Hookdeck's Celcoin source does not cover it, and neither does this skill.

## When to Use This Skill

* How do I receive Celcoin webhooks?
* How do I authenticate Celcoin webhooks with Basic auth?
* Does Celcoin sign webhook payloads? (No: static credentials only)
* How do I handle `pix-payment-in`, `pix-payment-out`, `spb-transfer-out` or `onboarding-proposal` events?
* Why does my Celcoin webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

const sha = (s) => crypto.createHash('sha256').update(s, 'utf8').digest();
const safeEqual = (a, b) => crypto.timingSafeEqual(sha(a), sha(b)); // equal-length digests

function verifyCelcoinAuth(authorization, username, password) {
  if (!username || !password || typeof authorization !== 'string') return false;
  const m = /^basic[ \t]+(\S+)$/i.exec(authorization.trim()); // scheme is case-insensitive
  if (!m || !/^[A-Za-z0-9+/]+={0,2}$/.test(m[1]) || m[1].length % 4) return false; // strict base64
  const decoded = Buffer.from(m[1], 'base64').toString('utf8');
  const i = decoded.indexOf(':'); // split on the FIRST colon
  if (i === -1) return false;
  const userOk = safeEqual(decoded.slice(0, i), username);
  const passOk = safeEqual(decoded.slice(i + 1), password); // evaluate both, no short-circuit
  return userOk && passOk;
}

```

* Header: `Authorization: Basic base64(username:password)`. Read the credentials from
  `CELCOIN_WEBHOOK_USERNAME` and `CELCOIN_WEBHOOK_PASSWORD`.
* Decode strictly (base64 alphabet only; `validate=True` in Python) so valid credentials followed by
  junk are rejected. The full examples also re-encode and compare in Node.
* Compare the username and the password each in constant time and evaluate both before deciding.
  Python: `hmac.compare_digest` on bytes (encode both sides; `str` raises on non-ASCII).
* Return `401` for a missing or malformed header or a mismatch, `400` for a body that isn't a JSON
  object, `200` once handled.
* **Hookdeck never sends a 401 challenge** (no `WWW-Authenticate` round trip), so the credentials
  must arrive with every request.
* A static shared credential authenticates the **sender, not the body**, and gives **no replay
  protection** (no signature, no timestamp). Use HTTPS, and rotate the credential (update the
  subscription) if it leaks.
* Read the body raw (`express.raw({ type: '*/*' })`) and authenticate before parsing it.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/celcoin-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/celcoin-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/celcoin-webhooks/examples/fastapi/).

## HTTP Methods

Celcoin's docs don't name the HTTP method of the notification. The examples therefore accept
**GET, POST, PUT, PATCH and DELETE** on the webhook route, check the credential first for every
method, and acknowledge an empty body. Hookdeck's Celcoin source pre-selects POST without managing
the method, so if Celcoin uses any other method, **also allow that method on the Hookdeck source**.

## Event Payload and Entities

Every notification has the same envelope, and you dispatch on `entity`:

```json
{
  "entity": "pix-payment-in",
  "createTimestamp": "2025-05-07T17:30:00.7647117",
  "status": "CONFIRMED",
  "body": { "id": "e2726e30-c318-4e1f-b284-e37c14e6c4b2" }
}

```

`status` is `CONFIRMED` or `ERROR` (an `ERROR` notification also carries an `error` object with
`errorCode` and `message`). Subscriptions are registered per entity:

| Entity | Sent When |
| --- | --- |
| `pix-payment-out` | A Pix payment or transfer you sent is confirmed |
| `pix-payment-in` | A Pix is received |
| `pix-reversal-out` | A refund of a received Pix you issued is confirmed |
| `pix-reversal-in` | A refund of a Pix-out you made is received |
| `internal-transfer-in` | An internal transfer is received |
| `internal-transfer-out` | An internal transfer is sent |
| `spb-transfer-out` | A TED you sent is confirmed |
| `spb-transfer-in` | A TED is received |
| `onboarding-create` | A new account is created |
| `onboarding-proposal` | The onboarding proposal is decided (`Approved` or `Reproved`) |
| `kyc` | The KYC result of an account is available |

Celcoin documents many more entities (Pix Automático, DICT claims, charges, bill payments, cards).
See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/celcoin-webhooks/references/overview.md).

## Environment Variables

```bash
CELCOIN_WEBHOOK_USERNAME=your_webhook_login   # auth.login on the subscription
CELCOIN_WEBHOOK_PASSWORD=your_webhook_pwd     # auth.pwd on the subscription

```

## Local Development

Celcoin can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 celcoin --path /webhooks/celcoin

```

Use the printed URL as the `webhookUrl` of the subscription.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/celcoin-webhooks/references/overview.md) - What Celcoin webhooks are, entities and envelope
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/celcoin-webhooks/references/setup.md) - Register a subscription with Basic auth
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/celcoin-webhooks/references/verification.md) - Basic auth check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/celcoin-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests