Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jalmargyyk/ripe-updater/llms.txt

Use this file to discover all available pages before exploring further.

When NetBox fires a webhook after a prefix is created, updated, or deleted, RIPE Updater’s /update endpoint orchestrates the entire pipeline — from validating the request to writing the final INETNUM or INET6NUM object into the RIPE database. Understanding this flow helps you configure NetBox correctly, debug unexpected responses, and reason about edge cases like overlapping prefixes.

Processing pipeline

1

Authorization check

RIPE Updater compares the incoming Authorisation header against UPDATE_TOKEN. If they do not match, the request is rejected with HTTP 401.
if request.headers.get('Authorisation') != UPDATE_TOKEN:
    logger.error('token missmatch')
    abort(401)
When UPDATE_TOKEN is not set, it defaults to None. In that case, request.headers.get('Authorisation') also returns None for requests without the header, so the comparison evaluates to False and all requests pass through. Set UPDATE_TOKEN in your environment to enforce authentication.Configure the matching token in NetBox’s webhook Additional Headers field:
Authorisation: Token YOURTOKEN
2

Payload validation

RIPE Updater expects a JSON body (Content-Type: application/json). It validates two things before proceeding:
  1. webhook['model'] must equal 'prefix' — only prefix objects are supported.
  2. webhook['data']['custom_fields']['ripe_report'] must be present — this custom field drives all routing decisions.
Any missing key returns HTTP 400 with a descriptive error message.
3

ripe_report flag routing

The ripe_report boolean custom field on the NetBox prefix determines what action is taken:
ripe_reportNetBox eventAction
falseanyDelete the object from RIPE DB
truecreated or updatedPush (create or update) the object in RIPE DB
truedeletedDelete the object from RIPE DB
if ripe_report is not True:
    ripe.delete_object()
else:
    if webhook['event'] != 'deleted':
        ripe.push_object()
    else:
        ripe.delete_object()
4

ObjectBuilder parses prefix metadata

ObjectBuilder extracts all attributes needed to build the RIPE object from the webhook payload and live NetBox API calls:
  • prefix — taken directly from webhook['data']['prefix']
  • username — the NetBox user who triggered the webhook (webhook['username'])
  • country — resolved by walking the site’s region hierarchy until an ISO 3166-1 alpha-2 country name is found; falls back to DEFAULT_COUNTRY if the site has no region
  • org — looked up from the lir_org.json mapping by reading the lir custom field on the parent aggregate in NetBox
netbox_object = ObjectBuilder(webhook)
5

RipeObjectManager builds the RIPE object

RipeObjectManager receives the parsed ObjectBuilder and immediately:
  1. Determines the object type: inet6num for IPv6, inetnum for IPv4.
  2. Fetches the existing object from RIPE DB (if any) and saves a backup.
  3. Reads the ripe_template custom field to select the correct template and base template from templates.json.
  4. Merges dynamic attributes (prefix, netname, org, country) with template-defined and master-template-defined attributes to produce the final object body.
ripe = RipeObjectManager(netbox_object, backup)
6

RIPE REST API call

push_object() checks whether the object already exists in RIPE DB and chooses the correct HTTP method:
  • POST — object does not exist yet (create)
  • PUT — object already exists (update)
  • DELETE — triggered by delete_object() when ripe_report is false or the prefix was deleted in NetBox
def push_object(self):
    old_object = self.get_old_object()
    new_object = self.generate_object()

    if old_object:
        self.put_object(old_object, new_object)
    else:
        self.post_object(new_object)
All calls use RIPE_MNT_PASSWORD for authentication via the password query parameter.

Response codes

On success, /update returns HTTP 204 (no content). On any RipeUpdaterException the endpoint returns HTTP 500 with the exception message. Prefixes that are filtered out (too small or non-routable) return HTTP 200 with a short explanatory message — they are intentionally skipped, not treated as errors.

Overlap detection

When a POST to RIPE DB fails with HTTP 400, RIPE Updater checks whether an overlapping object already exists in the database. This is handled by RipeObjectManager.overlapped_with(), which searches the RIPE REST API for any existing object of the same type that covers the target prefix:
def overlapped_with(self):
    params = {
        'source': RIPE_DB,
        'type-filter': self.objecttype,
        'flags': 'no-referenced',
        'query-string': self.prefix
    }
    request = requests.get(self.searchurl, params=params, headers=RIPE_HEADERS)
    ...
If an overlapping object is found and its prefix differs from the prefix being created, RIPE Updater queries NetBox to check whether the overlapping prefix exists there as a prefix or aggregate (FetchData.authorize_delete_overlapped_candidate). If it does not exist in NetBox, the overlap is considered stale and is automatically deleted from RIPE DB before retrying the original POST. If the overlap exists in NetBox, creation is aborted and the error is included in the email report.

IPv4 vs. IPv6 object types

RIPE Updater maps the two IP families to distinct RIPE DB object types:
FamilyRIPE object typeStatus
IPv4inetnumASSIGNED PA
IPv6inet6numASSIGNED
IPv4 prefixes are converted from CIDR notation (192.0.2.0/24) to the legacy range format (192.0.2.0 - 192.0.2.255) required by the RIPE inetnum schema. This conversion is performed by format_cidr():
def format_cidr(prefix):
    network = ip_network(prefix)
    return f'{network[0]} - {network[-1]}'
IPv6 prefixes are passed through to RIPE DB in standard CIDR notation.

Prefix size filtering

Prefixes that are too small are silently skipped (HTTP 200, no RIPE DB write). The limits are controlled by two environment variables:
VariableDefaultMeaning
SMALLEST_PREFIX_V431IPv4 prefix lengths greater than this are ignored
SMALLEST_PREFIX_V6127IPv6 prefix lengths greater than this are ignored
For example, with the defaults a /32 IPv4 host route is ignored, and a /128 IPv6 host route is ignored.
if network.prefixlen > int(SMALLEST_PREFIX_V4):
    raise ErrorSmallPrefix(f'This prefix is too small update only bigger than {SMALLEST_PREFIX_V4}')

Private and non-routable network filtering

Prefixes that are not publicly routable are also silently skipped. For IPv4, this covers loopback, reserved, private (RFC 1918), and multicast ranges. For IPv6, link-local, loopback, reserved, private, multicast, and any non-global addresses are additionally excluded:
# IPv4
if network.is_loopback or \
        network.is_reserved or \
        network.is_private or \
        network.is_multicast:
    raise NotRoutedNetwork('This is not routed prefix, it will be ignored')

# IPv6
if network.is_loopback or \
        network.is_reserved or \
        network.is_private or \
        network.is_multicast or \
        network.is_link_local or \
        not network.is_global:
    raise NotRoutedNetwork('This is not routed prefix, it will be ignored')
Both ErrorSmallPrefix and NotRoutedNetwork return HTTP 200 — they are expected and handled conditions, not errors.

Build docs developers (and LLMs) love