ARMHED
Log inCreate account

Webhooks

Every status change on a charge or payout is sent as an event to the endpoints you register in the dashboard under Webhooks. For each endpoint you choose which statuses to receive and set a signing secret.

Event

POST to your endpointjson
{
  "id": "evt_3f9c1a7b2d4e6f8091a2b3c4",
  "object": "event",
  "type": "charge.paid",
  "created_at": "2026-10-31T12:03:12Z",
  "data": {
    "object": "charge",
    "id": "6dcf2aee0d6148e1a12b78db78",
    "reference": "order-1042",
    "status": "paid",
    "amount": 4990,
    "currency": "BRL",
    "pix": { "end_to_end_id": "E22896431202610311207sjUGQflhcVE" },
    "payer": { "name": "ANA SOUZA", "document": "24577481600" },
    "created_at": "2026-10-31T12:00:00Z",
    "updated_at": "2026-10-31T12:03:11Z",
    "paid_at": "2026-10-31T12:03:11Z"
  }
}

data is a charge or a payout in the same shape as the API, without the fields that only exist on creation (such as pix.qr_code).

Event types

charge.created
Charge created, waiting for payment
charge.pending
Payment seen, being confirmed
charge.paid
Money received
charge.failed
Charge failed or expired
charge.refunded
Charge returned to the payer
payout.created
Payout queued
payout.processing
Payout sent to the bank
payout.paid
Money delivered to the recipient
payout.failed
Payout refused; data.failure says why and the amount is back in your balance
payout.returned
The recipient bank sent the money back

Headers

Armhed-Event-Idstring
Same as the body id. Stays the same across retries: use it to ignore duplicates
Armhed-Signaturestring
t=<unix seconds>,v1=<hex HMAC-SHA256>. Sent when the endpoint has a signing secret
Armhed-Redeliverystring
true when the event was resent manually from the dashboard

Verifying the signature

Compute HMAC-SHA256 with your secret over <t>.<raw body>, compare it to v1 in constant time, and reject events whose t is more than five minutes old. Use the raw request body, before any JSON parsing.

Node.js (Express)javascript
import crypto from "node:crypto"
import express from "express"

const app = express()

app.post("/webhooks/armhed", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("Armhed-Signature") ?? ""
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")))
  const expected = crypto
    .createHmac("sha256", process.env.ARMHED_WEBHOOK_SECRET)
    .update(`${t}.${req.body}`)
    .digest("hex")

  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300
  const valid = v1 && v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
  if (!fresh || !valid) return res.sendStatus(400)

  const event = JSON.parse(req.body)
  // dedupe on event.id, then handle event.type
  res.sendStatus(204)
})

Delivery and retries

  • Reply with any 2xx within 15 seconds. Do slow work after replying.
  • Any other status, a timeout, or a body that says false, failed or error counts as a failure.
  • Failed deliveries are retried 1, 5 and 15 minutes later. After that the event stays in the dashboard log, where you can resend it.
  • Events can arrive more than once and out of order. Dedupe on id and, when in doubt, retrieve the charge or payout from the API.
  • Deliveries come from 144.126.159.43 and 2605:a140:2365:9572::1 if you want to allowlist them.