Guide to Arcadia Webhooks: Features and Best Practices
Arcadia webhooks tell your systems when something changes in the utility data Arcadia collects for you. A credential starts failing to log in, a new bill statement is discovered, interval data arrives for a meter, or a user is asked for a multi-factor authentication code. If you build on Arcadia's Plug API, webhooks replace polling and let you fetch, store or act on new data as soon as Arcadia has it.
This guide covers how Arcadia routes webhooks through business units, the event types and payload shape, how to authenticate requests with a custom header that Arcadia support configures, how retries work, and the practices that keep an Arcadia receiver reliable.
What are Arcadia webhooks?
Arcadia sends a JSON POST to the webhook URLs you configure on a business unit. Each business unit can have a live URL and a sandbox URL. When an entity changes, Arcadia sends the webhook to every business unit related to it and to all of those business units' ancestors, if they have a URL configured. Each URL gets its own webhook with a distinct id, and the payloads are otherwise identical.
There is no per-event subscription. Every URL receives every event type, and Arcadia's own advice is to filter on the top-level resource and eventType fields and acknowledge the rest with a 2xx.
Arcadia doesn't sign webhooks. The only authentication it documents is a set of custom headers or query parameters sent with every webhook, and you can't set those yourself: Arcadia support configures them for your organisation on request.
Arcadia webhook features
| Feature | Details |
|---|---|
| Configuration | Arcadia Dashboard, Business units > a business unit > Webhooks panel |
| URLs | A live URL and a sandbox URL per business unit |
| Fan-out | Sent to related business units and their ancestors, one webhook id per URL |
| Event selection | None. Every URL receives every event type |
| Method and format | POST, JSON |
| Event field | eventType, with the entity type in resource |
| Authentication | Custom headers or query parameters set by Arcadia support |
| Header limits | Name up to 80 characters, value up to 255. Alphanumerics, _ and -; values also allow /, ., \ and ,. No spaces |
| Signature and timestamp | None |
| Response window | A 2xx within 10 seconds after the connection is made |
| Retries | Three times every six hours for a day, twelve retries at most, with an identical body |
| RTCV retries | At most three |
| Resending | API endpoints list and resend stored webhooks |
| Source IPs | Not published |
Event types
Arcadia's event types span several resources, so a router needs both eventType and resource to know what happened.
eventType | resource values | Sent when |
|---|---|---|
STATUS_CHANGE | CREDENTIAL, ACCOUNT, FILE, DELETION, DOWNLOAD, METER | A status or status detail changes, including account and meter voiding |
NEW_DATA_AVAILABLE | STATEMENT, DISCOVERED_STATEMENT, METER | A statement is discovered for the first time, or a new meter is discovered |
EXISTING_DATA_UPDATED | STATEMENT, DISCOVERED_STATEMENT, METER | A stored statement changes, or new interval data is available for a meter |
REAL_ | CREDENTIAL | Arcadia logged in to the utility account, for credentials created with isInteractive: true |
REAL_ | CREDENTIAL | Arcadia failed to log in, for credentials created with isInteractive: true |
MFA_CHALLENGE_METHOD | MFA_METHOD_PROMPT | The user has to choose an MFA method |
MFA_CHALLENGE_CODE | MFA_CODE_PROMPT | The user has to enter an MFA code |
BUSINESS_UNIT_ASSIGNMENT | ROOT_ | An account is assigned to the root business unit. Sent only to the root business unit's URL |
DATA_DELETED | BUSINESS_UNIT_DELETION | A user deletes a business unit |
The two MFA types appear in Arcadia's examples and scenarios rather than in its event tables. They are part of the interactive "Connect" flow, together with the real-time credential validation (RTCV) events.
Arcadia treats an adjustment statement as a new statement with its own ID, so it arrives as NEW_DATA_AVAILABLE. When Arcadia improves its extraction logic, a statement can be re-sent as EXISTING_DATA_UPDATED with the same statement ID and slightly different data.
Payload structure
Every webhook has the same envelope. Here is a trimmed STATUS_CHANGE for a credential, from Arcadia's examples:
{
"businessUnitIds": ["bus_b2d4e1d1-3028-becd-1821-ff80aa8107f5"],
"createdAt": "2024-02-21T00:00:00.000+00:00",
"data": {
"id": "crd_0c5d396f-f11c-c76b-9be8-89343463af21",
"connectionHealth": "CONNECTION_SUCCESS",
"status": "CONNECTION_SUCCESS",
"statusDetail": "LOGIN_AND_DATA_DISCOVERY_SUCCESS",
"totalAccounts": 1
},
"errors": [],
"eventType": "STATUS_CHANGE",
"id": "whk_9ec2fbdc-33ff-13b2-9b97-47d9ee7dcc73",
"resource": "CREDENTIAL"
}
data holds the same representation the API returns for that resource, so you often don't need a follow-up call. For statement events, it matches the summarised statement endpoint, not the detailed one. businessUnitIds lists only the directly related business units, not the ancestors that also received the webhook.
Inspect Arcadia webhooks as they arrive. Set a Hookdeck Inspect URL as a sandbox webhook URL on a business unit to see the exact headers and body Arcadia sends, with no account or setup required.
Setting up Arcadia webhooks
Pick a header name and a long random value first. Because spaces aren't allowed, the value can't be Bearer <token>, so use a custom header such as X-Arcadia-Token with a bare token, for example from openssl rand -hex 32. That keeps the value within Arcadia's character set.
- Open the Arcadia Dashboard, go to Business units and open the business unit's details.
- Edit the Webhooks panel and enter your HTTPS endpoint as the live or sandbox webhook URL.
- Open a support ticket with Arcadia through Zendesk and ask them to add your header name and value to your webhooks. Arcadia sets these values internally; they can't be set through the API or the Dashboard.
- Wait for support to confirm, then trigger an event, for example by creating a test credential in sandbox, and check that the header arrives.
The header applies to every business unit in your organisation, so one value covers all your URLs. Arcadia can also add query parameters, but a header is the better choice: query strings end up in access logs.
With Hookdeck. Arcadia is a supported source in Hookdeck. Create a source with the Arcadia type and use its URL as the webhook URL. Enter the header name you asked for in API Key Header and its exact value in API Key. Header names match case-insensitively; the value must match exactly. The source accepts POST only, which is what Arcadia sends. It checks the header, not a query parameter, so ask support for a header. A request with a missing or wrong value still gets a 200 from Hookdeck, but Hookdeck marks it as failed verification and doesn't deliver it to your service.
For local development, hookdeck listen 3000 arcadia --path /webhooks/arcadia gives you a public URL that forwards to your machine.
Securing Arcadia webhooks
With no signature, verification means reading the header Arcadia support configured and comparing its whole value with the one you stored, in constant time. Reject the request if the header is missing or the value isn't configured.
const crypto = require('crypto');
const express = require('express');
const HEADER_NAME = (process.env.ARCADIA_WEBHOOK_HEADER || 'x-arcadia-token').toLowerCase();
const SECRET = process.env.ARCADIA_WEBHOOK_SECRET;
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/arcadia', express.raw({ type: '*/*' }), (req, res) => {
const received = req.get(HEADER_NAME); // case-insensitive lookup
if (!SECRET || typeof received !== 'string' || !safeEqual(received, SECRET)) {
return res.sendStatus(401);
}
let event;
try {
event = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
// Route on the pair; acknowledge anything you don't handle.
const key = `${event.eventType}:${event.resource}`;
if (key === 'NEW_DATA_AVAILABLE:STATEMENT') {
// Queue a job to store the new statement in event.data.
}
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
HEADER_NAME = os.environ.get("ARCADIA_WEBHOOK_HEADER", "X-Arcadia-Token")
SECRET = os.environ.get("ARCADIA_WEBHOOK_SECRET", "")
def verify_arcadia_request(headers) -> bool:
if not SECRET:
return False # never accept when unconfigured
received = headers.get(HEADER_NAME) # Starlette and Flask headers are case-insensitive
if not received:
return False
# Compare bytes: compare_digest raises TypeError on a non-ASCII str.
return hmac.compare_digest(received.encode("utf-8"), SECRET.encode("utf-8"))
A static header proves only that the sender knows the value. It doesn't cover the body, so a modified payload still passes, and there is no timestamp, so a captured request can be replayed. Use HTTPS only, keep the value out of logs, and ask Arcadia support to change it if it might have leaked.
Make Arcadia webhooks production-ready. Hookdeck Event Gateway checks the header Arcadia sends, answers within Arcadia's 10-second window, and queues every webhook so you can retry and replay deliveries on your own schedule.
Arcadia webhook limitations and pain points
You can't set or rotate the secret yourself
The Problem: Adding authentication, or changing a leaked value, takes a support ticket and a wait.
Why It Happens: Arcadia sets custom headers and query parameters internally. They can't be changed through the API or the Dashboard.
Workarounds:
- Ask for the header when you first set up webhooks, not later.
- To rotate, accept both the old and the new value in your receiver until Arcadia confirms the change, then drop the old one.
How Hookdeck Can Help: Hookdeck's Arcadia source checks the header for you and keeps every request, including the ones that fail verification, with full headers, so you can see which value Arcadia is sending during a rotation.
Every URL gets every event
The Problem: A service that only cares about new statements still receives credential status changes, MFA prompts and meter updates.
Why It Happens: Arcadia doesn't support opting in or out of event types, and the fan-out to ancestor business units means a parent URL also sees its children's events.
Workarounds:
- Route on
eventTypeandresourceand return a2xxfor anything you don't handle, as Arcadia recommends. - Use
businessUnitIdsto tell which business unit an event belongs to.
How Hookdeck Can Help: Hookdeck filters can route on eventType and resource in the body, so each of your services only receives the events it handles.
Ten seconds, then hours of waiting
The Problem: A slow handler or a short outage turns into a delay of six hours or more.
Why It Happens: Arcadia waits 10 seconds for a 2xx after connecting. A failed delivery is retried three times every six hours for a day, and RTCV webhooks only three times in total.
Workarounds:
- Acknowledge first and process asynchronously, as Arcadia recommends for anything longer than 10 seconds.
- Use Arcadia's endpoints to list and resend stored webhooks after an outage.
How Hookdeck Can Help: Hookdeck answers Arcadia as soon as it has stored the request, then delivers to your service with retries you configure. You can replay any event after a fix without waiting for Arcadia's next attempt.
Duplicates across business units
The Problem: The same change can reach your system several times.
Why It Happens: One webhook is created per URL, so a child and a parent business unit pointing at the same service each get a copy with a different id. Retries resend the same body, with the same id.
Workarounds:
- Deduplicate retries on
id. - Deduplicate fan-out copies on the resource:
resource,data.idandeventType, plus a field that changes between real updates, such as the status.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeats based on fields you choose, before they reach your service.
Best practices
Ask for a header, not a query parameter
Arcadia can add either, but URLs are logged in many more places than headers. A custom header with a bare token, such as X-Arcadia-Token, fits Arcadia's character rules and is what Hookdeck's source checks.
Treat data as the API representation
data matches what the API returns for the resource, so you can usually store it without another call. Remember that statement webhooks carry the summarised statement; fetch the detailed statement if you need line items.
Make handlers idempotent
Store id before acting and skip repeats. For statements, key on the statement ID, since EXISTING_DATA_UPDATED re-sends a known statement with revised values. See our guide to webhook idempotency.
Acknowledge fast and process asynchronously
Verify the header, store the body, return 200, and do the work in a background job. See why to process webhooks asynchronously.
Handle the interactive flow promptly
If you build your own Connect experience, the MFA and RTCV events drive what the user sees next. Push them to the user's session quickly, and don't rely on retries: RTCV webhooks are retried at most three times.
Conclusion
Arcadia sends every event type to every webhook URL on a business unit and its ancestors, as JSON with eventType, resource and the API representation of the resource in data. There is no signature, so ask Arcadia support for a custom header with a long random value, compare it in full and in constant time, route on eventType and resource, and deduplicate on id and the resource.
Hookdeck Event Gateway checks that header, answers within Arcadia's 10-second window, and lets you filter, retry and replay events, so an outage on your side doesn't leave you waiting six hours for Arcadia's next attempt.
Get started with Hookdeck for free and handle Arcadia webhooks reliably in minutes.