# Guide to Smile API Webhooks: Features and Best Practices

Smile API webhooks notify your application when a user's account connects, a data-pull task finishes, or income and employment records are added. If you're building on Smile API for employment and income data in Southeast Asia, webhooks are how you react to these events without polling.

This guide covers how Smile API webhooks work, the events you'll handle, how to verify the `Smile-Signature` (it's SHA-512), and the best practices for production.

> This covers Smile API (getsmileapi.com), the Southeast Asia employment/income data aggregator. It is not Smile.io (loyalty) or Smile Identity (KYC), which are unrelated companies.

## What are Smile API webhooks?

Smile API webhooks are HTTP POSTs delivered over HTTPS to a URL you register. Each is signed with a `Smile-Signature` header (note: no `X-` prefix). The value is an HMAC-SHA512 (not SHA256) hex digest of the entire raw, unmodified request body, keyed with the per-endpoint `secret` (1 to 64 characters) you set at registration. This is not Standard Webhooks.

## Smile API webhook features

| Feature | Details |
| --- | --- |
| Configuration | portal.getsmileapi.com/webhooks or `POST /webhooks`; set a per-endpoint secret |
| Signature header | `Smile-Signature` (no `X-` prefix) |
| Signature scheme | HMAC-SHA512 (hex) over the entire raw body, keyed with the per-endpoint secret |
| Events | UPPER_SNAKE_CASE (`ACCOUNT_CONNECTED`, `TASK_FINISHED`, ...); `ALL_EVENTS` subscribes to everything |
| Delivery | At-least-once; non-2xx retried up to 2 times seconds apart, dedupe on `id` |
| Payload | Optional `includePayload` inlines full data (max 300 list items) for `TASK_FINISHED` / `ACCOUNT_SYNC_TASK_FINISHED` |
| Source IP | Static `18.142.61.230` (HTTPS only) |
| SDK | None |

## Common events

Smile API events are UPPER_SNAKE_CASE. There are roughly 35 types, some with `_ADDED` / `_UPDATED` variants. A common set:

| Event | Fires when |
| --- | --- |
| `ACCOUNT_CONNECTED` | A user connects an account |
| `TASK_FINISHED` | A data-pull task finishes |
| `INCOMES_ADDED` | Income records are added |
| `EMPLOYMENTS_ADDED` | Employment records are added |
| `IDENTITY_ADDED` | Identity data is added |
| `RECORD_COMPLETED` | A record completes |

Subscribe to `ALL_EVENTS` to receive everything, or pick the specific UPPER_SNAKE_CASE names you handle.

## Setting up Smile API webhooks

Create webhooks at portal.getsmileapi.com/webhooks or via `POST /webhooks`, and set a per-endpoint `secret` (1 to 64 characters) for verification. Deliveries originate from the static IP `18.142.61.230` over HTTPS, so you can allowlist it as an extra layer.

## Securing Smile API webhooks

The `Smile-Signature` header is an HMAC-SHA512 hex digest of the entire raw body, keyed with your per-endpoint secret. To verify, compute the SHA-512 HMAC over the raw body and compare. Digest the full raw payload with no leading or trailing whitespace, not re-serialized JSON.

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

const SECRET = process.env.SMILE_WEBHOOK_SECRET;

function verify(rawBody, signature) {
  // HMAC-SHA512 (not SHA256) over the entire raw body
  const expected = crypto.createHmac("sha512", SECRET).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["smile-signature"])) {
    return res.sendStatus(401);
  }

  res.sendStatus(200); // acknowledge fast
  processQueue.add(JSON.parse(req.body)); // dedupe on id, async
});

```

The same check in Python:

```python
import hashlib
import hmac
import os

SECRET = os.environ["SMILE_WEBHOOK_SECRET"].encode()

def verify(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(SECRET, raw_body, hashlib.sha512).hexdigest()  # SHA-512, not 256
    return hmac.compare_digest(expected, signature or "")

```

## Smile API webhook limitations and pain points

### It's SHA-512, and the header has no `X-` prefix

The Problem: The HMAC is SHA-512, not SHA-256, and the header is `Smile-Signature` with no `X-` prefix. A SHA-256 verifier, or code that looks for `X-Smile-Signature`, fails.

Why It Happens: Smile API uses SHA-512 and a non-prefixed header name.

Workarounds:

* Use SHA-512 over the raw body, and read the exact `Smile-Signature` header.

How Hookdeck Can Help: Hookdeck verifies the signature at the edge with the right algorithm and header, so your app doesn't hard-code either.

### It's Smile API, not Smile.io or Smile Identity

The Problem: Three unrelated companies share the "Smile" name. Applying one's verification or event model to another fails.

Why It Happens: The name collides across loyalty (Smile.io), KYC (Smile Identity), and data aggregation (Smile API).

Workarounds:

* Confirm you're integrating getsmileapi.com, and use its SHA-512 `Smile-Signature` scheme.

How Hookdeck Can Help: Hookdeck verifies each source with its own scheme, so a name collision doesn't lead to the wrong verification.

### At-least-once delivery

The Problem: Non-2xx responses are retried up to two times seconds apart, so the same event can arrive more than once.

Why It Happens: Smile API guarantees at-least-once delivery.

Workarounds:

* Dedupe on the event `id` and make handlers idempotent.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, so retries don't double-process. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Payload is inlined only with a flag

The Problem: Full data is inlined only when `includePayload` is set (and capped at 300 list items) for `TASK_FINISHED` / `ACCOUNT_SYNC_TASK_FINISHED`. Otherwise you fetch the data separately.

Why It Happens: Smile API keeps payloads lean unless you opt in.

Workarounds:

* Set `includePayload` where you need inline data, and fetch via the API otherwise.

How Hookdeck Can Help: Hookdeck durably queues events so your worker can enrich them via the API at its own pace.

## Best practices

### Verify the SHA-512 HMAC over the raw body

Compute HMAC-SHA512 over the entire raw body with your per-endpoint secret and compare against `Smile-Signature` in constant time.

### Dedupe on the event id

Delivery is at-least-once, so dedupe on `id` and make handlers idempotent.

### Allowlist the static source IP

Deliveries come from `18.142.61.230` over HTTPS, so allowlisting it adds a layer alongside signature verification.

### Return 200 and process asynchronously

Acknowledge quickly and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Smile API (getsmileapi.com) webhooks are verified with a `Smile-Signature` header, an HMAC-SHA512 over the entire raw body keyed with your per-endpoint secret. This is Smile API, not Smile.io or Smile Identity. Verify SHA-512 over the raw body, use the UPPER_SNAKE_CASE event names, dedupe on `id` across the at-least-once retries, and optionally allowlist the static source IP.

[Hookdeck](https://hookdeck.com) verifies the signature, deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Smile API webhooks reliably in minutes.