# Webhook

Webhooks allow you to receive real-time notifications whenever events occur in your workspace, such as payments received, transfers completed, or boletos paid.

Instead of polling the API for updates, you register a URL and Stark Bank will send a POST request to it every time a relevant event happens.

Every webhook event is signed with a digital signature that our SDKs verify automatically, ensuring the authenticity of each notification.

Below you will learn how to subscribe to events, handle them securely, and build a resilient integration.

NOTE: Read Core Concepts before continuing this guide.

**RESOURCE SUMMARY**

WebhookSubscribe to events by registering your endpoint URL.EventProcess and verify the events delivered to your endpoint.Best PracticesSecurity, resilience, and reliability recommendations.

## Webhook Overview

A Webhook subscription tells Stark Bank where to send event notifications. You provide an HTTPS URL and a list of event types you want to receive.

Your endpoint must use a valid TLS/SSL certificate — self-signed certificates are not supported.

Available subscriptions: deposit, invoice, brcode-payment, transfer, utility-payment, boleto, boleto-payment, darf-payment, payment-request, boleto-holmes.

Subscribe only to the event types your application needs. This reduces unnecessary traffic and simplifies your event handler.

### Creating a Webhook

Register a new webhook by providing your endpoint URL and the event types you want to subscribe to.

You can subscribe to one or more event types at once. Each webhook will only receive events for the subscriptions you specify.

**Request**

```python
import starkbank

webhook = starkbank.webhook.create(
    url="https://winterfell.westeros.gov/events-from-stark-bank",
    subscriptions=[
        "boleto",
        "boleto-payment",
        "transfer",
        "utility-payment"
    ]
)

print(webhook)
```

**Response**

```python
Webhook(
    id=6225875037061120,
    subscriptions=['boleto', 'boleto-payment', 'transfer', 'utility-payment'],
    url=https://winterfell.westeros.gov/events-from-stark-bank
)
```

### Listing Webhooks

You can list all active webhook subscriptions to check your current configuration.

**Request**

```python
import starkbank

webhooks = starkbank.webhook.query()

for webhook in webhooks:
    print(webhook)
```

**Response**

```python
Webhook(
    id=6225875037061120,
    subscriptions=['boleto', 'boleto-payment', 'transfer', 'utility-payment'],
    url=https://winterfell.westeros.gov/events-from-stark-bank
)
```

### Deleting a Webhook

Delete a webhook subscription when you no longer want to receive events at that URL.

This action cannot be undone.

**Request**

```python
import starkbank

webhook = starkbank.webhook.delete("6225875037061120")

print(webhook)
```

**Response**

```python
Webhook(
    id=6225875037061120,
    subscriptions=['boleto', 'boleto-payment', 'transfer', 'utility-payment'],
    url=https://winterfell.westeros.gov/events-from-stark-bank
)
```

## Event Overview

Every time a log is created for a subscribed resource, Stark Bank generates an Event and delivers it to your webhook URL via a POST request.

Each event contains a subscription field indicating the resource type (e.g. transfer, invoice) and a log property with the details of what changed. The nature of the log depends on the subscription that triggered the event.

### Handling Webhook Events

When Stark Bank sends an event to your webhook URL, it includes a Digital-Signature header. Use the SDK's event.parse() method to both parse the event and verify the signature automatically.

The SDK verifies the signature using Stark Bank's public key, ensuring the event was genuinely sent by Stark Bank. Never process events without verifying the signature.

**Request**

```python
import starkbank

request = listen()  # this is the method you made to get the events posted to your Webhook endpoint

event = starkbank.event.parse(
    content=request.data.decode("utf-8"),
    signature=request.headers["Digital-Signature"],
)

if "transfer" in event.subscription:
    print(event.log.request)
```

### Getting an Event

You can get a specific event by its ID to inspect its details.

**Request**

```python
import starkbank

event = starkbank.event.get("4537025176797184")

print(event)
```

**Response**

```python
Event(
    id=4537025176797184,
    is_delivered=False,
    subscription=transfer,
    created=2020-04-24 17:49:10.181589,
    log=Log(
        id=5662318377566208,
        created=2020-04-24 17:49:09.348444,
        errors=[],
        type=sending,
        transfer=Transfer(
            id=5950134772826112,
            account_number=76543-8,
            amount=100000000,
            bank_code=665,
            branch_code=2201,
            created=2020-04-24 17:49:08.748893,
            fee=200,
            name=Daenerys Targaryen Stormborn,
            status=processing,
            tags=['daenerys', 'invoice/1234'],
            tax_id=594.739.480-42,
            transaction_ids=['5991715760504832'],
            updated=2020-04-24 17:49:09.632271
        )
    ),
    workspace_id=1231231231231231
)
```

### Listing Events

You can list all events that match your filters. Use the isDelivered parameter to filter events that haven't been successfully delivered.

This is especially useful for building a resilience mechanism: periodically query for undelivered events, process them, and mark them as delivered.

**Request**

```python
import starkbank

events = starkbank.event.query(
    is_delivered=False,
    after="2020-04-01",
    before="2020-04-30"
)

for event in events:
    print(event)
```

**Response**

```python
Event(
    id=4537025176797184,
    is_delivered=False,
    subscription=transfer,
    created=2020-04-24 17:49:10.181589,
    log=Log(
        id=5662318377566208,
        created=2020-04-24 17:49:09.348444,
        errors=[],
        type=sending,
        transfer=Transfer(
            id=5950134772826112,
            account_number=76543-8,
            amount=100000000,
            bank_code=665,
            branch_code=2201,
            created=2020-04-24 17:49:08.748893,
            fee=200,
            name=Daenerys Targaryen Stormborn,
            status=processing,
            tags=['daenerys', 'invoice/1234'],
            tax_id=594.739.480-42,
            transaction_ids=['5991715760504832'],
            updated=2020-04-24 17:49:09.632271
        )
    ),
    workspace_id=1231231231231231
)
```

### Updating an Event

After processing an event, you can mark it as delivered by updating the isDelivered property to true.

This is particularly useful after recovering from server downtime: list all undelivered events, process them, and then set them as delivered.

**Request**

```python
import starkbank

event = starkbank.event.update(
    "4537025176797184",
    is_delivered=True
)

print(event)
```

**Response**

```python
Event(
    id=4537025176797184,
    is_delivered=True,
    subscription=transfer,
    created=2020-04-24 17:49:10.181589,
    log=Log(
        id=5662318377566208,
        created=2020-04-24 17:49:09.348444,
        errors=[],
        type=sending,
        transfer=Transfer(
            id=5950134772826112,
            account_number=76543-8,
            amount=100000000,
            bank_code=665,
            branch_code=2201,
            created=2020-04-24 17:49:08.748893,
            fee=200,
            name=Daenerys Targaryen Stormborn,
            status=processing,
            tags=['daenerys', 'invoice/1234'],
            tax_id=594.739.480-42,
            transaction_ids=['5991715760504832'],
            updated=2020-04-24 17:49:09.632271
        )
    ),
    workspace_id=1231231231231231
)
```

## Best Practices

Follow these recommendations to build a secure and resilient webhook integration.

### Verify Digital Signatures

Every event delivered to your webhook URL includes a header:

Digital-Signature

This signature is generated using Stark Bank's private key and can be verified using the public key available at:

GET /v2/public-key

Our SDKs handle this verification automatically when you use:

event.parse()

Never process events without verifying the signature first — this ensures the event was genuinely sent by Stark Bank and has not been tampered with.

### Allowlist Stark Bank Static IPs

Stark Bank sends webhook events from fixed IP addresses. Add them to your firewall ingress rules to block requests from unknown sources:

Production: 35.199.76.124, 34.85.188.162

Sandbox: 35.247.226.240, 35.245.182.229

Combining IP allowlisting with digital signature verification provides defense in depth for your webhook endpoint.

### Return 200 Quickly

Your webhook handler should only do three things synchronously: validate the Digital Signature, save the event to your database, and return HTTP 200. Never process the event before returning 200.

All event processing — such as updating records, triggering payments, or sending notifications — must be done asynchronously in a segregated background task. Use a message queue or job scheduler to decouple receiving from processing.

If your endpoint does not return a 200 status, Stark Bank will retry the delivery according to the following schedule:

| Attempt | Delay |
| --- | --- |
| 1st retry | 5 minutes |
| 2nd retry | 30 minutes |
| 3rd retry | 120 minutes |

After 3 failed attempts, the delivery will stop. Use Event Polling to recover any missed events.

### Handle Duplicates and Out-of-Order Events

Events may be delivered more than once and are not guaranteed to arrive in order. Your handler must be idempotent — processing the same event twice should not cause incorrect behavior.

Guard against duplicates by logging processed event IDs and checking before processing. Do not rely on the order of event delivery — always check the current state of the resource rather than assuming a sequence.

This is especially critical for financial operations where duplicate processing could result in incorrect balances or duplicate payments.

### Handle Unfamiliar Event Types Gracefully

Stark Bank may add new event types to webhooks at any time. Adding new events is not a breaking change.

Your webhook listener should gracefully handle unfamiliar event types — for example, by logging them and returning 200 — rather than failing or returning an error. This ensures your integration remains stable as the platform evolves.

### React to the Right Event

More important than the order events arrive is observing the correct event to trigger your actions. Each resource goes through a lifecycle, and different events represent different stages.

For example, if you need an Invoice to be paid to complete a sale, wait for the paid event. But if you need the Invoice amount to be credited in your account before making a Transfer, wait for the credited event instead.

Reacting to the wrong event can lead to race conditions — such as trying to transfer funds that haven't been credited yet. Always match your business logic to the specific event that guarantees the precondition you need.

### Exempt Your Webhook Route from CSRF Protection

If your web framework enforces CSRF token validation (e.g. Django, Rails, Laravel), your webhook endpoint will reject Stark Bank's POST requests because they don't include a CSRF token.

Exempt your webhook route from CSRF protection. The Digital Signature verification already guarantees the request authenticity.

### Test in Sandbox First

Stark Bank provides a full Sandbox environment that simulates the complete event lifecycle — creation, status changes, and webhook delivery — without real money.

Register your webhook URL in Sandbox, trigger operations (e.g. create an Invoice or Transfer), and verify that your endpoint receives and processes events correctly before going live.

Sandbox and Production use separate workspaces with different API keys. Webhooks registered in one environment will not receive events from the other.

### Build Resilience with Event Polling

Even with webhooks, we strongly recommend creating a daily task to:

1. Query all undelivered events using:

GET /v2/event?isDelivered=false

2. Process all undelivered events

3. Mark them as delivered using:

PATCH /v2/event/:id 

isDelivered=true

This adds redundancy and resilience to your system, preventing you from having outdated information in case your system is temporarily unable to receive webhook events.

---

## Other Languages

- [Python](https://docs.starkbank.com/get-started/webhook-python.md)
- [Node.js](https://docs.starkbank.com/get-started/webhook-node.md)
- [PHP](https://docs.starkbank.com/get-started/webhook-php.md)
- [Java](https://docs.starkbank.com/get-started/webhook-java.md)
- [Ruby](https://docs.starkbank.com/get-started/webhook-ruby.md)
- [Elixir](https://docs.starkbank.com/get-started/webhook-elixir.md)
- [.NET](https://docs.starkbank.com/get-started/webhook-dotnet.md)
- [Go](https://docs.starkbank.com/get-started/webhook-go.md)
- [Clojure](https://docs.starkbank.com/get-started/webhook-clojure.md)
- [cURL](https://docs.starkbank.com/get-started/webhook-curl.md)
